File Processing
Submit PDFs for OCR processing and stream real-time progress via the REST API.
Upload and process a file
The recommended way to send a file through the pipeline. Handles upload and dispatch in a single request.
Headers
| Header | Required | Value |
|---|---|---|
x-api-key | Yes | Your tenant API key |
x-user-id | Yes | User to associate with this file |
Content-Type | Yes | multipart/form-data |
Body — multipart form with a single file field (PDF, max 64 MB).
Response 200 OK
Use fileId to poll progress or query records.
Example (cURL)
Example (Python)
Info
Browser upload? Use the two-phase flow below to avoid the file passing through your
server. The /tenant-upload-and-process endpoint above is ideal for server-to-server
or CLI usage.
Browser-direct upload (two-phase)
For browser clients where you don't want the file passing through your server:
Phase 1 — Request an upload token
Headers
| Header | Required | Value |
|---|---|---|
x-api-key | Yes | Your tenant API key (server-side only) |
x-user-id | Yes | User to associate with this file |
Content-Type | Yes | application/json |
Body
Response 200 OK
The token is valid for 2 minutes.
Phase 2 — Upload directly from the browser
Headers
| Header | Required | Value |
|---|---|---|
x-upload-token | Yes | Token from Phase 1 |
Content-Type | Yes | multipart/form-data |
Body — same as above: multipart file field, PDF only, max 64 MB.
Response 200 OK — same shape as the single-step endpoint: { success: true, fileId }.
Example (browser JavaScript)
Info
Using Next.js? The @nexa-ed/next SDK handles Phase 1 automatically via the
/api/nexa/prepare route. The useUploadFile hook and <UploadZone /> component
implement the full two-phase flow out of the box.
Submit a file for processing
Kick off the OCR pipeline for an already-uploaded file. Returns immediately with a jobId — processing happens asynchronously.
Warning
Rate limit: 20 requests / minute per tenant.
This endpoint calls Google Document AI and Gemini — it is the most expensive route on the platform.
Exceeding the limit returns HTTP 429 with a Retry-After header.
See Rate limits for retry guidance.
Headers
| Header | Required | Value |
|---|---|---|
x-api-key | Yes | Your tenant API key |
x-user-id | No | User to associate with this file |
Content-Type | Yes | application/json |
Body
| Field | Type | Required | Description |
|---|---|---|---|
fileUrl | string | Yes | Publicly accessible URL of the uploaded PDF |
fileId | string | Yes | Your identifier for this file |
fileName | string | No | Human-readable name |
extractionType | string | No | "document_ai" (default) or "tesseract" |
Response 202 Accepted
Example (cURL)
Example (Python)
Stream processing progress
Subscribe to real-time SSE events for a file. The stream closes automatically when processing completes or fails.
Headers
| Header | Required | Value |
|---|---|---|
x-api-key | Yes | Your tenant API key |
Accept | Yes | text/event-stream |
SSE events
Each event is a JSON object in the data: field:
| Field | Type | Description |
|---|---|---|
stage | string | "queued" | "ocr" | "analysis" | "complete" | "failed" |
progress | number | 0–100 |
message | string | Human-readable status |
fileId | string | Echoed on completion |
recordCount | number | Number of student records extracted (on complete) |
error | string | Error message (on failed) |
Example (Python with sseclient)
Example (Node.js EventSource)
Check file status
Lightweight polling alternative to SSE.
Response 200 OK
List processed files
Query params
| Param | Type | Default | Description |
|---|---|---|---|
userId | string | — | Filter by user |
limit | number | 20 | Max results |
offset | number | 0 | Pagination offset |
Response
Webhook — file completed
When processing finishes, Nexa sends a POST to your configured webhook URL.
Payload
See REST API Overview → Webhook verification for how to verify the signature.