/v1/documents/uploadUpload Document
Request a presigned S3 upload URL and receive a document_key. All inference APIs — Parse, Extract, Classify, and Split — accept this key instead of a raw file, so a single upload can be reused across multiple operations within 24 hours.
Get a presigned upload URL
Call POST /v1/documents/upload (through the API gateway) — returns a document_key and a presigned upload_url.
Upload the file bytes to S3
PUT directly to {upload_url}. No API key needed — the URL is pre-authenticated and valid for 15 minutes.
Request
Headers
| Name | Type | Required | Description |
|---|---|---|---|
X-API-Key | string | required | Your API key (hk_live_ or hk_test_). |
Content-Type | string | required | Must be application/json. |
Body application/json
| Name | Type | Required | Description |
|---|---|---|---|
filename | string | required | Original filename including extension (e.g. "invoice.pdf"). Used for MIME auto-detection if content_type is omitted. |
content_type | string | optional | MIME type of the file. Supported: application/pdf; images (image/png, image/jpeg, image/webp, image/tiff, image/heic); and Office/text formats (Word, Excel, PowerPoint, CSV, TXT, RTF, JSON, XML). Defaults to application/pdf. |
content_length | integer | optional | File size in bytes. Enables server-side size validation before the presigned URL is issued — the request is rejected with 413 above the applicable ceiling (50 MB by default; see Size limits below). |
Size limits
An upload is capped at 50 MB, and /v1/parse in mode=advanced at 60 pages. Both ceilings are raised for one specific case:
| Caller | Max upload | Max pages (advanced parse) |
|---|---|---|
| Dashboard or browser upload | 50 MB | 60 |
| API or SDK — any tier, synchronous | 50 MB | 60 |
| API or SDK — custom tier, X-Async: true | 512 MiB | 500 |
The raised limits apply only when all three conditions hold: your organization is on the custom tier, the request comes directly from the API or an official SDK (uploads made through the dashboard or the browser do not qualify, whatever your tier), and the inference request carries X-Async: true. Miss any one of them and an over-cap request returns the same 413 it always has. The page cap applies to mode=advanced only — mode=fast is not page-capped.
Page limits by operation
The 50 MB upload ceiling applies to every operation; page ceilings differ per operation and are checked when you submit the document_key — before any billable processing runs.
| Operation | Max pages | Notes |
|---|---|---|
| Parse (fast) | — | Never page-capped. |
| Parse (advanced) | 60 | Raised to 500 for the bulk case above. |
| Extract (fast) | — | Never page-capped. |
| Extract (advanced) | 60 | Same cap as advanced parse. |
| Classify | — | Reads the first pages it needs; long documents are fine. |
| Split | — | |
| Redact / Deidentify | 36 | Rejected up front with 413 before any processing is billed. |
Rather than hardcoding these numbers, read them at runtime: GET /v1/limits returns your organization's effective per-operation ceilings, and the same limits object rides on every upload response. Asynchronous submissions return 200 when the job is accepted — the final verdict, including a limit rejection, lands on the job record you poll.
A document over the standard cap is processed in segments and can take tens of minutes, so it is only ever returned as a job you poll. See Large documents for the polling contract and how partial failures are reported.
Code examples
# Step 1: Request a presigned S3 upload URL
RESP=$(curl -sX POST "https://apis.hyperbots.com/v1/documents/upload" \
-H "X-API-Key: hk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"filename":"document.pdf","content_type":"application/pdf"}')
KEY=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['document_key'])")
URL=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['upload_url'])")
# Step 2: PUT the file directly to S3 (no API key needed — URL is pre-authenticated)
curl -X PUT "$URL" \
-H "Content-Type: application/pdf" \
-H "x-amz-server-side-encryption: AES256" \
--data-binary @document.pdf
# document_key is now ready — pass it to /v1/parse, /v1/extract, etc.
echo "$KEY" # 550e8400-e29b-41d4-a716-446655440000# Step 1: Request a presigned S3 upload URL
RESP=$(curl -sX POST "https://apis.hyperbots.com/v1/documents/upload" \
-H "X-API-Key: hk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"filename":"document.pdf","content_type":"application/pdf"}')
KEY=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['document_key'])")
URL=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['upload_url'])")
# Step 2: PUT the file directly to S3 (no API key needed — URL is pre-authenticated)
curl -X PUT "$URL" \
-H "Content-Type: application/pdf" \
-H "x-amz-server-side-encryption: AES256" \
--data-binary @document.pdf
# document_key is now ready — pass it to /v1/parse, /v1/extract, etc.
echo "$KEY" # 550e8400-e29b-41d4-a716-446655440000Response
| Field | Type | Description |
|---|---|---|
document_keykey | string | UUID identifying this upload. Pass to any inference endpoint (Parse, Extract, Classify, Split) as a form field. |
upload_url | string | Pre-authenticated S3 PUT URL. Upload the file bytes directly here — no API key required. Valid for expires_in seconds. |
expires_in | integer | Presigned URL TTL in seconds (default 900 = 15 minutes). The file itself is retained in storage for 24 hours. |
{
"document_key": "550e8400-e29b-41d4-a716-446655440000",
"upload_url": "https://uploads.example-cdn.com/uploads/org_abc/550e8400-...?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=900&...",
"expires_in": 900
}