Skip to content
On this page Base URL
Documentation/API reference

Parser API v1

API reference

Everything needed to submit resumes, track parse jobs, fetch structured results, and receive parse events. All endpoints are project-scoped and authenticated with a project API key.

Base URL

Use https://cvault.tech as the production API origin. The /v1 parser API is served on this host. The examples use $CVAULT_API_URL and $CVAULT_API_KEY as environment placeholders.

Set your backend environment
export CVAULT_API_URL="https://cvault.tech"
export CVAULT_API_KEY="<your-project-key>"

Successful responses use the envelope { data, meta } with meta.api_version and meta.request_id. Most errors use application/problem+json; direct multipart upload validation has a separate JSON body.

Success envelope
{
  "data": {},
  "meta": {
    "api_version": "v1",
    "request_id": "req_..."
  }
}

Authentication

Create a project and an API key in the console, then send the key as x-api-key. Keys are environment-bound: cv_test_… for test projects and cv_live_… for live projects.

Example
x-api-key: cv_live_<your-key>

Public parser scopes: parse.read, parse.write, usage.read, schema.read and webhook.manage. A .write scope grants the matching .read operation. Keep the key on your backend; bearer tokens, cookies and query-string keys do not authenticate /v1.

Use an API-enabled workspace. Owners and managers create test or live projects at Projects and keys at API keys. The raw key is shown once; store it in a secret manager. Key management can require an MFA step-up. Rotation revokes the previous key immediately. A test prefix identifies the project environment; it does not mean parsing is simulated or unlimited.

Quickstart

Submit a resume and read the returned job ID. Parsing is asynchronous: poll GET /v1/parse-jobs/{job_id} until the status is terminal, or subscribe to parse webhooks.

curl -sS -X POST "$CVAULT_API_URL/v1/resumes/parse" \
  -H "x-api-key: $CVAULT_API_KEY" \
  -H "Idempotency-Key: first-resume-0001" \
  -F "files[]=@resume.pdf;type=application/pdf"

These examples submit a file; they do not wait for parsing to finish. Python needs requests; Node needs Node 20+ and an ESM file (.mjs). Store the job ID, then poll with results included:

Example
curl -sS "$CVAULT_API_URL/v1/parse-jobs/<job_id>?include=results&limit=50" \
  -H "x-api-key: $CVAULT_API_KEY"

# After parsing is terminal, retrieve later pages separately:
curl -sS "$CVAULT_API_URL/v1/parse-jobs/<job_id>/results?limit=50" \
  -H "x-api-key: $CVAULT_API_KEY"
Read every file outcome

Stop on succeeded, failed, cancelled, timed_out or expired; erasure adds erasing and erased. Nonterminal polls omit results. A succeeded job can contain failed files: inspect every item. Use a bounded polling deadline, honor Retry-After, and keep the original idempotency key if an upload response is lost. See the parsing guide for recovery.

Python and Node SDKs

SDK access

The official clients submit a single file, poll the job and return its projected data. Python supports synchronous and asynchronous clients (Python 3.10+); the Node client requires Node 20+ and runs on your backend. Use the HTTP examples above for publicly available onboarding. SDK installation below requires authorized access to a source checkout; a public package download is not verified.Contact CVault about SDK access before depending on these clients.

Only with an authorized SDK source checkout
python -m pip install ./sdks/python

# Node package: build before installing locally
npm --prefix sdks/typescript ci
npm --prefix sdks/typescript run build
npm install ./sdks/typescript
import os
from cvault_sdk import CVault

with CVault(api_key=os.environ["CVAULT_API_KEY"]) as client:
    data = client.parse("resume.pdf", idempotency_key="candidate-42-revision-7")
    print(data.get("contact"))

Store your own idempotency key per file revision to recover across process restarts. SDK polling defaults to 300 seconds; a polling timeout does not cancel the server job. Recover with the job ID carried by timeout or post-submission errors. The single-file helpers do not submit batches; use the HTTP flows below for batches and page through all results.

Endpoints

Examples use synthetic identifiers and illustrative values; they show valid response subsets, not a complete schema or plan allowance. Replace every placeholder before making a request. JSON examples require content-type: application/json and your x-api-key header.

GET

/v1/schema

Read the parser response schema

Required scope: schema.read

Returns the parser version, mode, top-level field names and advertised evidence modes. This is a capability summary, not a nested JSON Schema or OpenAPI document. Responses are marked no-store.

No request parameters. Send your project API key in the x-api-key header.

Integration notes
  • Standard mode only; precision_enabled is false.
  • Advertised evidence modes do not establish a selectable full-evidence response contract. The current submission path does not expose an evidence-mode switch. Inspect the returned projection and use the workspace schema when applicable.
Request · cURL
curl -sS "$CVAULT_API_URL/v1/schema" \
  -H "x-api-key: $CVAULT_API_KEY"
Response · illustrative subset
{
  "data": {
    "schema_version": "cvault.parser-response.v1",
    "parser": {
      "version": "1.0.3",
      "mode": "standard",
      "precision_enabled": false
    },
    "fields": [
      "contact",
      "summary",
      "experience",
      "education",
      "skills",
      "languages",
      "certifications"
    ],
    "evidence_modes": [
      "none",
      "compact",
      "full"
    ]
  },
  "meta": {
    "api_version": "v1",
    "request_id": "req_8f1c..."
  }
}
POST

/v1/resumes/parse

Parse resumes directly (multipart)

Required scope: parse.write

Submits one or more resume files in a single multipart request. `POST /v1/parse-jobs` is an alias of this endpoint. Parsing is asynchronous: the response returns a job ID that you poll or receive by webhook.

Request parameters

Idempotency-Keystringheaderrequired

8–200 characters. Alternative: X-CVault-Idempotency-Key or the idempotency_key form/JSON field. All supplied values must agree.

files[]file[]formrequired

One or more files as multipart/form-data. Send each file with its filename and content type. File, batch and account limits apply.

job_descriptionstringform

Optional job context. Upload sessions limit this to 50,000 UTF-8 bytes.

parse_optionsJSON stringform

Standard parser options. Upload sessions limit normalized options to 20,000 bytes; precision, LLM-extra and evidence-mode switches are not exposed.

parser_workspace_idUUIDform

Bind an accessible processing workspace. Its configuration revision is saved with the job. See the workspace guide for API restrictions.

webhook_subscription_idUUIDform

Select an active subscription in the same project/environment. Without it, matching active project subscriptions are used.

client_metadataJSON stringform

String values only: at most 16 pairs, 64-byte keys, 256-byte values and 2,048 serialized UTF-8 bytes in total.

Integration notes
  • Use multipart/form-data; repeat files[] for every file. Let your HTTP library set the boundary.
  • Fields: files[] (required), idempotency_key (unless supplied by header), parse_options (JSON object string), job_description (text), webhook_subscription_id (project subscription UUID), client_metadata (JSON object string), and parser_workspace_id (processing workspace UUID).
  • Metadata: at most 16 string pairs, nonempty keys up to 64 UTF-8 bytes, values up to 256 bytes and a serialized object up to 2,048 bytes. Do not put resume content, secrets or callback URLs in metadata.
  • Idempotency is required. Retry the same file bytes, form fields and operation with the same key. Changed content returns 409 idempotency_conflict.
  • New asynchronous jobs return 202; a completed or cached submission can return 200. Store data.job_id in either case. Examples show a subset of the response; dispatch metadata and candidate_shells can also be present.
Request · cURL
curl -sS -X POST "$CVAULT_API_URL/v1/resumes/parse" \
  -H "x-api-key: $CVAULT_API_KEY" \
  -H "Idempotency-Key: first-resume-0001" \
  -F "files[]=@resume.pdf;type=application/pdf"
Response · illustrative subset
{
  "data": {
    "success": true,
    "job_id": "b7f3c0f0-...",
    "status": "queued"
  },
  "meta": {
    "api_version": "v1",
    "request_id": "req_8f1c..."
  }
}
POST

/v1/upload-sessions

Create an upload session

Required scope: parse.write

Declare whole files, upload each file independently to its signed PUT URL, then complete the session. This separates upload from parsing; it is not a chunked resumable-upload protocol.

Request parameters

Idempotency-Keystringheaderrequired

8–200 characters. Alternative: X-CVault-Idempotency-Key or the idempotency_key form/JSON field. All supplied values must agree.

filesobject[]bodyrequired

Declare 1–1,000 files. Each needs a valid filename and positive size_bytes; provide content_type for the upload. Actual size must match the declaration exactly.

job_descriptionstringbody

Optional job context. Upload sessions limit this to 50,000 UTF-8 bytes.

parse_optionsobjectbody

Standard parser options. Upload sessions limit normalized options to 20,000 bytes; precision, LLM-extra and evidence-mode switches are not exposed.

parser_workspace_idUUIDbody

Bind an accessible processing workspace. Its configuration revision is saved with the job. See the workspace guide for API restrictions.

webhook_subscription_idUUIDbody

Select an active subscription in the same project/environment. Without it, matching active project subscriptions are used.

client_metadataobjectbody

String values only: at most 16 pairs, 64-byte keys, 256-byte values and 2,048 serialized UTF-8 bytes in total.

Integration notes
  • Returns 201. Send application/json and a valid idempotency key. Each file declaration includes filename, positive size_bytes and content_type.
  • The actual object size must match size_bytes. A mismatch rejects completion with HTTP 415 unsupported_file; object_size_mismatch is an internal rejection reason.
  • Use each upload target once for its declared file. Do not send your CVault API key to the storage URL, and treat that signed URL as a temporary secret.
  • Complete before accept_before (10 minutes). The provider token lasts 7,200 seconds, but this does not extend the session acceptance deadline.
  • Upload sessions allow at most 1,000 files, 10 MiB per file and 50 MiB total by default. Admission and account limits can impose lower limits.
Request body · JSON
{
  "files": [
    {
      "filename": "resume.pdf",
      "size_bytes": 184320,
      "content_type": "application/pdf"
    }
  ],
  "job_description": "Backend engineer shortlist",
  "idempotency_key": "batch-2026-09-14-0001"
}
Response · illustrative subset
{
  "data": {
    "session_id": "0f2f5a56-...",
    "status": "uploading",
    "accept_before": "2026-09-14T12:10:00+00:00",
    "provider_upload_token_ttl_seconds": 7200,
    "upload_targets": [
      {
        "file_id": "9c2d...",
        "method": "PUT",
        "signed_url": "https://<storage-host>/...",
        "accept_before": "2026-09-14T12:10:00+00:00"
      }
    ]
  },
  "meta": {
    "api_version": "v1",
    "request_id": "req_8f1c..."
  }
}
POST

/v1/upload-sessions/{session_id}/complete

Complete an upload session

Required scope: parse.write

Validates the uploaded objects, scans them, and submits the batch for parsing. Completion is idempotent per session; repeating it returns the same accepted job response once the job is bound.

Request parameters

session_idstringpathrequired

The session ID returned by declaration.

Idempotency-Keystringheaderrequired

8–200 characters. Alternative: X-CVault-Idempotency-Key or the idempotency_key form/JSON field. All supplied values must agree.

Integration notes
  • A valid idempotency key is required for completion. The body may be omitted only when the key is supplied by Idempotency-Key or X-CVault-Idempotency-Key. Use a separate key from session creation.
  • Returns 202 when accepted (or 200 for a completed outcome). Repeating the same completion returns the bound job. Completion validates objects and malware scan evidence.
  • Unknown IDs return 404; completion already running returns 409; expired, cancelled or rejected sessions return 410. Time-budget exhaustion can return 408. Handle the actual problem code and retryable flag.
Request · cURL
curl -sS -X POST "$CVAULT_API_URL/v1/upload-sessions/0f2f5a56-.../complete" \
  -H "x-api-key: $CVAULT_API_KEY" \
  -H "content-type: application/json" \
  -d '{"idempotency_key":"batch-2026-09-14-0001-complete"}'
Response · illustrative subset
{
  "data": {
    "success": true,
    "job_id": "b7f3c0f0-...",
    "status": "queued"
  },
  "meta": {
    "api_version": "v1",
    "request_id": "req_8f1c..."
  }
}
GET

/v1/parse-jobs/{job_id}

Poll a parse job

Required scope: parse.read

Read job progress. Stop polling on succeeded, failed, cancelled, timed_out, expired or erased. Erasure reports erasing until cleanup completes.

Request parameters

job_idstringpathrequired

The job ID returned by submission.

includestringquery

Set to results to include a result page when parsing is terminal. Nonterminal and erasure responses omit the page.

limitintegerquery

1–100; defaults to 50.

cursorstringquery

Pass the opaque next_cursor from the previous page. Keep the same key; results pages also require the same limit.

Integration notes
  • Statuses: queued, running, succeeded, failed, cancelled, timed_out, expired, erasing, erased. A succeeded batch can contain failed or duplicate files; inspect each result.
  • done counts processed files including failures and timeouts. estimated_seconds_remaining can be null and is not a latency guarantee.
  • Add ?include=results to include a results page when parsing is terminal. limit and cursor apply to that page. Nonterminal, erasing and erased status responses omit the page.
Request · cURL
curl -sS "$CVAULT_API_URL/v1/parse-jobs/b7f3c0f0-..." \
  -H "x-api-key: $CVAULT_API_KEY"
Response · illustrative subset
{
  "data": {
    "success": true,
    "job_id": "b7f3c0f0-...",
    "status": "running",
    "total": 3,
    "done": 2,
    "failed": 0,
    "timed_out": 0,
    "progress_pct": 66,
    "estimated_seconds_remaining": 12
  },
  "meta": {
    "api_version": "v1",
    "request_id": "req_8f1c..."
  }
}
GET

/v1/parse-jobs/{job_id}/results

Fetch parse results

Required scope: parse.read

Returns the parsed documents for a job, page by page. Each item contains the candidate data, per-file success state, and duplicates detected in the batch.

Request parameters

job_idstringpathrequired

The job ID returned by submission.

limitintegerquery

1–100; defaults to 50.

cursorstringquery

Pass the opaque next_cursor from the previous page. Keep the same key; results pages also require the same limit.

Integration notes
  • limit is 1-100, default 50. Pass next_cursor as the cursor query parameter; keep the same limit and API key. Cursors are opaque and bound to the job, project, environment and key.
  • The outer success flag describes retrieval. Each item has status (succeeded or failed), success, filename, data and a safe error when present. Pending items may have no data; wait for a terminal job before treating results as final.
  • Duplicate items have duplicate: true, data: null and success: false. They are not newly parsed documents. duplicates describes the current page.
  • parses_used and parses_limit are workspace parse-quota counters, not the organization parser-credit balance. A null parses_limit means an unbounded plan quota.
  • Read every page until next_cursor is null. Canonical parser data is extensible; the example shows only a subset of fields, not a complete nested schema.
Request · cURL
curl -sS "$CVAULT_API_URL/v1/parse-jobs/b7f3c0f0-.../results?limit=50" \
  -H "x-api-key: $CVAULT_API_KEY"
Response · illustrative subset
{
  "data": {
    "success": true,
    "job_id": "b7f3c0f0-...",
    "status": "succeeded",
    "total": 1,
    "done": 1,
    "results": [
      {
        "filename": "resume.pdf",
        "success": true,
        "data": {
          "contact": {},
          "experience": [],
          "education": [],
          "skills": []
        },
        "error": null,
        "duplicate": false,
        "duplicate_candidate_id": null,
        "status": "succeeded"
      }
    ],
    "parses_used": 1,
    "parses_limit": null,
    "has_more": false,
    "duplicates": [],
    "next_cursor": null
  },
  "meta": {
    "api_version": "v1",
    "request_id": "req_8f1c..."
  }
}
DELETE

/v1/parse-jobs/{job_id}

Cancel a parse job

Required scope: parse.write

Requests cancellation for a queued or running job. Documents already completed are retained. Cancelling a terminal job is a conflict.

Request parameters

job_idstringpathrequired

The job ID returned by submission.

Integration notes
  • Returns 200 when cancelled, including an already-cancelled job; other terminal states return 409 parse_conflict. No idempotency header is required.
  • Cancellation retains completed documents and does not erase data. Use the erasure endpoint for deletion.
Request · cURL
curl -sS -X DELETE "$CVAULT_API_URL/v1/parse-jobs/b7f3c0f0-..." \
  -H "x-api-key: $CVAULT_API_KEY"
Response · illustrative subset
{
  "data": {
    "job_id": "b7f3c0f0-...",
    "status": "cancelled"
  },
  "meta": {
    "api_version": "v1",
    "request_id": "req_8f1c..."
  }
}
DELETE

/v1/parse-jobs/{job_id}/erasure

Erase a parse job

Required scope: parse.write

Fences queued or running parser work, deletes accepted source objects, and erases durable results and cached copies. The operation is idempotent. Poll the job until `status` is `erased`; results are unavailable while erasure is pending.

Request parameters

job_idstringpathrequired

The job ID returned by submission.

Integration notes
  • Returns 200 with status: erased when cleanup is complete, or 202 with status: erasing and cleanup_pending: true while reconciliation retries. Poll until erased.
  • Once accepted, results return 410 parse_job_erasure_in_progress. Cancellation is not erasure. The idempotent DELETE needs no idempotency header.
  • Erasure covers this job source/results/cache. Operational audit and metering identities remain. Separately persisted processing-workspace documents, exports and receiver copies require separate lifecycle handling; do not assume organization-wide deletion.
Request · cURL
curl -sS -X DELETE "$CVAULT_API_URL/v1/parse-jobs/b7f3c0f0-.../erasure" \
  -H "x-api-key: $CVAULT_API_KEY"
Response · illustrative subset
{
  "data": {
    "job_id": "b7f3c0f0-...",
    "status": "erasing",
    "cleanup_pending": true
  },
  "meta": {
    "api_version": "v1",
    "request_id": "req_8f1c..."
  }
}
GET

/v1/usage

Read project metering events

Required scope: usage.read

Lists per-document metering events for this project, newest first. This is an event ledger, not a monthly allowance or parser-credit balance endpoint.

Request parameters

limitintegerquery

1–100; defaults to 50.

cursorstringquery

Pass the opaque next_cursor from the previous page. Keep the same key; results pages also require the same limit.

Integration notes
  • limit is 1-100 (default 50); send cursor=<next_cursor> to continue.
  • request_id in a usage event references the request ledger row; /v1/requests exposes its correlation request_id separately.
  • document_units records document usage. charged_units belongs to a separate developer billing ledger; the current metering writer records zero charged units. This does not make parsing free: organization parser credits are separate.
  • Additional fields include event_key and estimated_compute_usd when present. Estimates are not invoice prices.
Request · cURL
curl -sS "$CVAULT_API_URL/v1/usage?limit=50" \
  -H "x-api-key: $CVAULT_API_KEY"
Response · illustrative subset
{
  "data": {
    "items": [
      {
        "id": "usage-event-uuid",
        "project_id": "project-uuid",
        "request_id": "request-row-uuid",
        "job_id": "job-uuid",
        "file_id": "file-uuid",
        "outcome": "succeeded",
        "lane": "native",
        "document_units": 1,
        "charged_units": 0,
        "billing_mode": "sandbox",
        "parser_version": "1.0.3",
        "attempt_count": 1,
        "occurred_at": "2026-09-14T10:24:12+00:00"
      }
    ],
    "next_cursor": null
  },
  "meta": {
    "api_version": "v1",
    "request_id": "req_example"
  }
}
GET

/v1/requests

List API request history

Required scope: usage.read

Lists project request ledger outcomes, HTTP status codes, job IDs, document counts and timing metadata. Use it to reconcile submissions by request and job identity.

Request parameters

limitintegerquery

1–100; defaults to 50.

cursorstringquery

Pass the opaque next_cursor from the previous page. Keep the same key; results pages also require the same limit.

Integration notes
  • Returns sanitized request ledger records, newest first. id identifies the ledger row; request_id is the correlation ID. The example shows a subset of available outcome and timing fields.
  • Internal response payloads, fingerprints, storage paths, hashes and secrets are excluded. Raw idempotency keys are not returned by this endpoint.
  • Use limit (1-100, default 50) and cursor=<next_cursor>. Keep cursors opaque and use the same project/key.
Request · cURL
curl -sS "$CVAULT_API_URL/v1/requests?limit=50" \
  -H "x-api-key: $CVAULT_API_KEY"
Response · illustrative subset
{
  "data": {
    "items": [
      {
        "request_id": "req_8f1c...",
        "method": "POST",
        "path": "/v1/resumes/parse",
        "status": "accepted",
        "document_count": 1,
        "created_at": "2026-09-14T10:24:12+00:00"
      }
    ],
    "next_cursor": null
  },
  "meta": {
    "api_version": "v1",
    "request_id": "req_8f1c..."
  }
}
POST

/v1/webhook-subscriptions

Create a webhook subscription

Required scope: webhook.manage

Creates a project/environment subscription and returns secret once in the 201 creation response. Store it securely to verify deliveries. Console organization endpoints are a distinct scope.

Request parameters

target_urlstringbodyrequired

A publicly reachable HTTPS URL without embedded credentials. Private, loopback and metadata-service destinations are blocked.

event_typesstring[]bodyrequired

One or more supported events: parse.accepted, parse.completed, parse.failed, parse.timed_out or parse.cancelled.

Integration notes
  • Use a publicly reachable HTTPS endpoint without URL credentials. Private, loopback and metadata-service destinations are blocked; redirects are not followed.
  • Supported events: parse.accepted, parse.completed, parse.failed, parse.timed_out, parse.cancelled. These are job transitions; inspect individual results after completion.
  • Use webhook_subscription_id on a submission to select an active subscription in the same project/environment. Otherwise matching active project subscriptions are used. Request-level callback URLs are rejected.
  • There are no public /v1 list, update, test or secret-rotation routes for these subscriptions. Do not confuse console organization endpoint controls with the developer project API.
Request body · JSON
{
  "target_url": "https://hooks.example.com/cvault",
  "event_types": [
    "parse.completed",
    "parse.failed"
  ]
}
Response · illustrative subset
{
  "data": {
    "id": "1d1f...",
    "project_id": "9a7e...",
    "target_url": "https://hooks.example.com/cvault",
    "event_types": [
      "parse.completed",
      "parse.failed"
    ],
    "is_active": true,
    "secret": "whsec_<store-this-once>"
  },
  "meta": {
    "api_version": "v1",
    "request_id": "req_8f1c..."
  }
}

Idempotency

Submission and upload-session mutations require Idempotency-Key or its documented alternatives. Cancellation and erasure use idempotent DELETE requests without that header.

  • Required for multipart submission, upload-session creation and completion. Use Idempotency-Key, X-CVault-Idempotency-Key, or the idempotency_key form/JSON field. All supplied values must match; otherwise 400 idempotency_key_required.
  • Keys are 8-200 characters, begin with a letter or digit and then use letters, digits, dot, underscore, colon or hyphen.
  • Within a project, replaying the same key, operation, content and fields returns the recorded outcome. Keep the key across timeouts and process restarts; recorded responses may retain their original request ID.
  • Changed content or fields with the same key returns 409 idempotency_conflict. An in-progress replay returns 409 idempotency_in_progress with Retry-After.
  • Use distinct keys for session creation and completion. Cancellation and erasure are idempotent DELETE operations and need no idempotency header.
Example
Idempotency-Key: <8-200 chars: letters, digits, . _ : ->

Rate limits

Three independent buckets in a 60-second window. Submission shares key/project/workspace counters; job reads and metadata use separate per-key counters. The window is 60 seconds.

  • Response headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
  • 429 rate_limit_exceeded with Retry-After; the error body is application/problem+json.
BucketOperationsAllowance
SubmissionPOST and DELETEConfigured base limit; shared key/project/workspace counters
ReadGET job status and results10 times base limit, capped at 1,200/minute per key
MetadataGET schema, usage and requests2 times base limit, capped at 1,200/minute per key

Read the actual X-RateLimit-Limit for the current operation rather than assuming a plan value. X-RateLimit-Reset is the value 60 in seconds, not a Unix timestamp. Admission concurrency, document quotas and parser credits are additional checks independent of these buckets.

File rules

RuleValue
Supported extensions.pdf, .docx, .doc, .txt, .png, .jpg, .jpeg
Maximum file size10 MiB per file
Maximum batch size50 MiB per request (configurable per deployment)
Maximum upload-session files1,000
FilenameAt most 255 characters; no path separators, NUL, CR or LF

The batch byte limit measures file contents; multipart framing has a separate bounded allowance. Direct multipart batches also pass deployment admission and account limits; do not assume that 1,000 files is a universal direct-upload cap. Filenames must be nonempty and cannot be . or .. . Extension alone is insufficient: content, type, document resource limits and malware scans are validated.

Errors

Most errors are application/problem+json with a stable code, a retryable flag, and the request ID for support.

Example error
{
  "type": "https://api.cvault.app/problems/quota_exceeded",
  "title": "Quota Exceeded",
  "status": 403,
  "code": "quota_exceeded",
  "detail": "The project allowance has been exceeded.",
  "retryable": false,
  "request_id": "req_8f1c...",
  "field_errors": []
}
Upload validation uses a different error body

Direct multipart validation is an exception: HTTP 413 (size/resource limits), 415 (invalid content/type) or 503 (scanner unavailable) returns a direct JSON body before admission, without a job or data/meta envelope. Inspect error_code and validation_results. Generic validation failures may have only per-file codes. Size/type errors require corrected input; scanner outages are retryable with the same submission key.

Direct validation body (illustrative subset)
{
  "success": false,
  "error": "Upload validation failed",
  "admission_state": "rejected_before_admission",
  "retryable": true,
  "error_code": "malware_scan_unavailable",
  "validation_results": []
}

The table covers common transport errors, not every possible validation or admission reason. Read HTTP status, code (or error_code), retryable and Retry-After together. Preserve X-Request-Id for support. The problem type URI identifies the error category; it is not the API origin.

CodeStatusMeaning
api_key_required401The x-api-key header is missing.
invalid_api_key401The API key is invalid or no longer active.
project_key_required403The key is not bound to an active test/live project.
api_key_scope_required403The key lacks the scope required by the endpoint.
api_access_not_enabled403API access is not enabled for this workspace.
quota_exceeded403Workspace plan quota or a project/key daily/monthly document cap is exhausted.
rate_limit_exceeded429The project rate limit was exceeded; honor Retry-After.
idempotency_key_required400A valid idempotency key was not supplied.
idempotency_conflict409The idempotency key was reused with a different payload.
idempotency_in_progress409The original idempotent request is still processing.
files_required400The multipart request contained no files[] parts.
unsupported_media_type415Use multipart/form-data or application/json as documented.
payload_too_large413The request body exceeds the documented limit.
upload_validation_failed415A file failed validation; inspect the direct multipart validation body for per-file codes and its HTTP status.
upload_session_not_found404The upload session does not exist for this project.
parse_job_not_found404The job ID is malformed, unknown or unavailable to this project.
invalid_limit400limit must be an integer from 1 to 100.
invalid_cursor400The cursor is invalid or bound to a different resource/key/environment/results-page limit.
webhook_input_invalid422target_url or event_types are missing or invalid.
webhook_https_required422Webhook targets must use HTTPS.
webhook_events_invalid422No supported event remains in event_types; unknown entries are filtered. Supply only documented events.
webhook_subscription_id_invalid422The subscription ID is malformed, inactive or unavailable in the project/environment.
precision_not_enabled422Only standard parser mode is available on the public V1 API.
parse_service_unavailable503The parser is temporarily unavailable; retry with backoff.
service_unavailable503The service is temporarily unavailable; retry with backoff.
insufficient_parser_credits402Organization parser credits are insufficient; add credits before retrying.
client_metadata_invalid422Metadata exceeds the documented object/string limits.
parse_options_invalid400Multipart parse_options must be a JSON object string.
invalid_json400The request body is not valid JSON.
json_body_required400A JSON object is required.
request_destination_forbidden422Request-level callback or webhook destinations are unsupported.
parse_conflict409The operation conflicts with the current job or session state.
parse_job_erasure_in_progress410Source and results are being erased or have been erased.
unsupported_file415An uploaded session object failed validation, including declared-size mismatch.
parse_timeout408Upload-session validation or completion exceeded its time budget.
rate_limiter_unavailable503Rate-limit enforcement is unavailable; honor Retry-After.
parser_credit_unavailable503Credit accounting is temporarily unavailable.
parser_erasure_unavailable503Retry the same erasure request to finish cleanup.
parser_workspace_id_invalid422Processing workspace ID is malformed or conflicts with parse_options.
parser_workspace_not_found404The processing workspace is unavailable to this project actor.
parser_workspace_ocr_unsupported501Public asynchronous workspace submissions require automatic OCR.
parser_workspace_splitting_unsupported501Page splitting is unsupported in the public asynchronous flow.
parser_workspace_memory_unsupported501Model memory is unsupported in the public asynchronous flow.
parser_workspace_auto_classify_unsupported501Automatic classification is unsupported in the public asynchronous flow.
parser_workspace_persistence_unavailable503Retry results retrieval to finish saving the workspace document.

Before production integration

  • Keep API keys and signed upload URLs out of browsers, URLs, source control and logs.
  • Use an API-enabled project with sufficient credits, scopes and document allowance.
  • Persist submission keys and job IDs; recover ambiguous uploads without generating new keys.
  • Handle partial failures, duplicate files, every results page and a bounded polling deadline.
  • Authenticate webhook bytes, persist delivery IDs atomically and durably enqueue before acknowledging.
  • Use erasure for deletion and wait for erased; cancellation alone retains completed documents.
  • Use /v1 for this parser contract. The separate /api/v1 platform API and its OpenAPI document are not this API.