# HumanPen API

Upload a document and create an asynchronous processing job in one request, then poll progress and download the editable result.

## Machine-readable resources

- [OpenAPI 3.1 schema](https://api.test.humanpen.net/v1/openapi.json)
- [Agent documentation index](https://api.test.humanpen.net/v1/llms.txt)

## Base URL

`https://api.test.humanpen.net/v1`

## Authentication

Send an API key in every protected request:

```http
Authorization: Bearer hp_xxx
```

API keys are intended for server-side use. Do not expose them in browser bundles, mobile apps, public repositories, or client logs.

## Core workflow

1. `POST` the document and the job options to the endpoint for the operation you want, as multipart form data. The 201 response is a receipt: `job_id` to poll, plus `credits_frozen` - the credits reserved for this job.
2. Poll `GET /jobs/{job_id}` until `finished` is `true`. Poll the `finished` boolean, not the status enum, so new status values never break your loop. A reasonable cadence is every 5 seconds, backing off toward 30; jobs typically take minutes.
3. On success, `result.download_url` is a short-lived presigned URL: a plain `GET` with no Authorization header downloads the document. If it expired, fetch the job again for a fresh one. `result.retained_until` is when the file leaves storage - the job record itself is kept.

A job that does not complete is not charged: `credits.charged` is the settled amount on `DONE` and always `0` on `ERROR` or `CANCELLED`.

API keys must send multipart form data; a JSON body returns `API_KEY_MULTIPART_REQUIRED`. `POST /files` is reserved for the interactive web flow — API keys receive `API_KEY_STANDALONE_UPLOAD_FORBIDDEN` and must use an endpoint such as `POST /jobs/humanize`, so standalone uploads cannot be used as general-purpose object storage.

## Operations

### Reduce AI-detection signals

Rewrite a DOCX so it reads as human-written, keeping the meaning, the citations, and the layout.

```bash
curl -X POST "https://api.test.humanpen.net/v1/jobs/humanize" \
  -H "Authorization: Bearer $ADP_API_KEY" \
  -F "file=@paper.docx" \
  -F "turnitin_file=@turnitin-report.pdf" \
  -F "strategy=balanced" \
  -F "additional_instructions=Keep terminology and citations unchanged."
```

`turnitin_file` is optional. When `selected_texts` is omitted, the passages detected in that report become the rewriting scope; when `selected_texts` is supplied, that list is final and the report is kept only as a job attachment.

### Correct citation format

Rewrite in-text citations and the reference list into one target style. The body text is left untouched.

```bash
curl -X POST "https://api.test.humanpen.net/v1/jobs/citation-format-correction" \
  -H "Authorization: Bearer $ADP_API_KEY" \
  -F "file=@paper.docx" \
  -F "citation_style=apa7"
```

Supported styles: `apa7`, `mla9`, `harvard`, `chicago_author_date`, `chicago_notes`, `ieee`, `vancouver`, `gbt7714`, `gbt7714_author_year`, `ama`, `acs`, `oscola`.

### Condense

Shorten the whole document to a word budget, or shorten only the passages you name, each to its own budget.

```bash
curl -X POST "https://api.test.humanpen.net/v1/jobs/condense" \
  -H "Authorization: Bearer $ADP_API_KEY" \
  -F "file=@paper.docx" \
  -F "max_words=4000"
```

Send exactly one of `max_words` or `segments`. Because a form field cannot carry an object array, `segments` is a JSON string, for example `-F 'segments=[{"text":"...","max_words":80}]'`. A passage is resolved to the paragraph that contains it, so the paragraph is the smallest unit actually processed.

### Translate a document

Translate a document into the target language, keeping the source formatting and returning the same file format.

```bash
curl -X POST "https://api.test.humanpen.net/v1/jobs/translate" \
  -H "Authorization: Bearer $ADP_API_KEY" \
  -F "file=@paper.docx" \
  -F "source_lang=auto" \
  -F "target_lang=zh"
```

Target languages: `zh`, `en`, `zh-tw`, `ja`, `ko`, `es`, `fr`, `pt`, `ru`, `de`, `pl`, `it`. `source_lang` accepts the same values plus `auto` (the default).

Successful JSON responses use `{ "code": "OK", "message": "ok", "data": ... }`. Errors use the same envelope with a stable error `code`. The `X-Request-ID` response header can be used for support and log correlation.

## Public endpoint reference

| Method | Path | Description |
| --- | --- | --- |
| `GET` | `/health` | Get API liveness details |
| `GET` | `/ready` | Get dependency readiness details |
| `POST` | `/turnitin-reports/parse` | Parse highlighted passages from a Turnitin PDF |
| `GET` | `/auth/me` | Get the authenticated principal and synchronized business-side user state |
| `POST` | `/auth/session/sync` | Create or update the business-side user mirror for an interactive session |
| `PATCH` | `/auth/me/locale` | Update the current user preferred locale |
| `GET` | `/auth/me/usage` | Get current-user document usage and configured quotas |
| `GET` | `/files` | List current user files |
| `POST` | `/files` | Upload a source document and extract document metadata |
| `GET` | `/files/{file_id}/preview` | Get stored file preview text |
| `POST` | `/files/{file_id}/selected-texts/validate` | Validate selected passages against a staged document |
| `GET` | `/files/{file_id}/download` | Create a short-lived download URL |
| `GET` | `/files/{file_id}/content` | Download a stored file through the API |
| `DELETE` | `/files/{file_id}` | Delete a stored file and revoke future access |
| `POST` | `/jobs/humanize` | Upload a DOCX and create a humanization job |
| `POST` | `/jobs/citation-format-correction` | Create a citation-format correction job |
| `POST` | `/jobs/condense` | Create a condense job |
| `POST` | `/jobs/translate` | Create a document translation job |
| `GET` | `/jobs` | List current user jobs |
| `GET` | `/jobs/{job_id}` | Get one job and temporary download URLs |
| `POST` | `/jobs/{job_id}/cancel` | Cancel a queued or running job |
| `POST` | `/jobs/{job_id}/retry` | Retry a failed or cancelled job |
| `GET` | `/api-keys` | List API keys for the current interactive user |
| `POST` | `/api-keys` | Create an API key for server-to-server use |
| `DELETE` | `/api-keys/{api_key_id}` | Revoke one API key |
