Skip to main content
POST
Ingest a batch of content items
Every valid item becomes its own durable ingest job, so a slow or failing item cannot hold up the rest of the batch; poll each one with GET /v1/ingest/{jobId}/status or list them with GET /v1/jobs. Items that cannot be queued (empty content, oversized content, a non-object metadata) are reported in results with a reason and counted in skipped; they never fail the batch. Jobs are written to the effective subject’s partition: the key owner by default, or a provisioned subject selected with the X-Exo-Subject header (an unprovisioned selector is refused by the shared subject seam before this handler runs). An Idempotency-Key makes the accept replay-safe: a retry with the same key and body returns the original envelope, with the original job ids, without queueing the batch a second time.

Authorization

This route requires the write scope. A missing or invalid credential returns 401 authentication_error. A valid credential without the scope returns 403 permission_denied, and the problem body names the exact scope required.

Headers

These are request conventions the contract does not declare as parameters, so they do not appear in the schema tables below.

Success responses

Errors

Beyond the shared statuses, this route can answer with idempotency_in_flight, idempotency_key_reuse. Every problem body names its own code, and the type URI always resolves to the matching page. Every error is an RFC 9457 application/problem+json body carrying a stable code, a requestId, and a suggestedAction where Exo has one. See Errors.

Notes

  • One call, many jobs. Every valid item becomes its own durable job, so one slow item cannot hold up the rest. jobIds lists them in the order they were queued.
  • Unqueueable items are reported, not fatal. Empty content, oversized content and a non-object metadata are counted in skipped and explained in results. The batch still returns 202.
  • A replayed Idempotency-Key returns the original envelope, including the original job ids, without queueing anything a second time.

Authorizations

X-Exo-API-Key
string
header
required

An Exo API key (exo_...) sent as the X-Exo-API-Key header.

Body

application/json

Body for POST /v1/ingest/batch.

items keeps the legacy free-form {content, source_type?, metadata?} element shape (plus the title the MCP already sends) so existing callers need no change; per-item validation is reported inside results rather than failing the whole batch. The reserved subject selector (spec 4.2) is consumed by the shared subject seam from the raw body and is deliberately NOT declared here, matching every other public request model.

items
Items · object[]
required

Content items to ingest. Each is an object with 'content' (required, up to 100000 characters) plus optional 'title', 'source_type' and 'metadata'. An item that fails validation is reported in 'results' and does not fail the batch.

Maximum array length: 500

Response

Every valid item was durably queued for ingestion

202 envelope for a batch accept.

The first four fields are the legacy wire contract, unchanged and in their original order; ingested / skipped / jobIds are additive.

failed
integer
required

total - succeeded. Includes skipped items (legacy arithmetic).

ingested
integer
required

Alias of 'succeeded' for batch-ingest clients.

jobIds
string[]
required

Job ids for the accepted items; poll GET /v1/ingest/{jobId}/status.

results
Results · object[]
required

Per-item outcome, in request order: accepted items carry {status:'accepted', node_id:'', chunks:0, jobId}; rejected items carry {status:'skipped', reason}.

skipped
integer
required

Items rejected before enqueue.

succeeded
integer
required

Items accepted for ingestion.

total
integer
required

Items received.