Skip to main content
Stages a document and returns an opaque file_id. Reference it in any job body as source_url: "ndi://file/<file_id>" — upload once, then parse, categorize, and ground the same file without re-sending the bytes.
POST /upload
Content-Type: multipart/form-data

Request

file
file
required
The document, as a multipart/form-data part named file. The filename you send is captured and used for format detection. Maximum size 200 MB (413 when exceeded).

Response

file_id
uuid
Opaque handle. Use it as ndi://file/<file_id> in any source_url.
file_name
string
The captured filename. Jobs started from this handle inherit it, so you can omit file_name in job bodies.
expires_at
datetime
When the handle stops working (~6 days after upload). A job started with an expired handle fails with 410 Gone — just upload the file again.

Notes

  • Handles are tenant-scoped: another tenant’s file_id returns 404, never your data.
  • Uploads are not deduplicated and take no idempotency_key — uploading the same file twice just yields two handles, which is harmless. Idempotency matters for jobs, not uploads.
  • Uploading does not start any processing and does not count against your concurrent-jobs limit (it does count toward requests-per-minute).

Example

curl -s "$NDI_BASE/upload" \
  -H "X-API-Key: $NDI_API_KEY" \
  -F "file=@./annual-report.pdf"
{
  "file_id": "7f4d2a10-3b7e-4c25-9f61-8f0f4f0a1b2c",
  "file_name": "annual-report.pdf",
  "expires_at": "2026-07-18T21:04:11Z"
}
Then start a job with it:
curl -s "$NDI_BASE/parse" \
  -H "X-API-Key: $NDI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "file": { "source_url": "ndi://file/7f4d2a10-3b7e-4c25-9f61-8f0f4f0a1b2c" } }'