Documents and tasks
How writes work
A document is one product in your index. Every document write is asynchronous. Zaproo checks the batch, stores it and creates a task. A worker then writes the documents to the index. The Tasks page, or the task id the API answers with, tells you when the documents are searchable and which ones failed.
Use uploads and deletes for day-to-day changes: a price, a stock level, a new product. To send the whole catalog and drop everything you did not send, use a full catalog sync instead. To have Zaproo fetch your product feed on a schedule, set up a data source.
Uploading and deleting needs the organization owner or admin role. Members can browse documents and tasks.
Upsert documents
An upsert adds documents, or replaces the ones whose id the index already holds. A replaced document is replaced as a whole: fields you leave out are gone from it afterwards, so always send the complete document.

Drop a JSON file on Upload documents
Open the index, then Documents, and drop a .json file on Upload documents, or click the area to choose one. See upload a file in the cabinet.
The body is a bare JSON array of documents, not an object around one. Every document has an id string of 1 to 512 bytes.
curl -X POST https://api.search.zaproo.cloud/v1/projects/$PROJECT_ID/indices/$INDEX_ID/documents \
-H "Authorization: Bearer $ZS_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: catalog-2026-10-16-batch-1" \
-d '[
{"id": "tape-48", "title": "Brown packaging tape 48mm", "brand": "Demo Store", "price": 1.86, "attributes": {"color": "brown"}},
{"id": "box-300", "title": "Cardboard box 300x200x200mm", "brand": "Demo Store", "price": 0.32, "attributes": {"color": "brown"}}
]'
# 202 {"taskId": "0b9f7c1e-3a52-4f7e-9d0b-6a1c2e4d8f10"}
Upload the documents in ./catalog.json to the products index and tell me when the task is done.
The agent calls index_documents with up to 1000 documents per call, then get_task.
/v1/projects/:projectId/indices/:indexId/documentsProject admin or a full-access secret keyBefore it creates the task, Zaproo checks every document against the mapping. If any document has a value of the wrong type or the same id appears twice in the batch, the whole batch is refused with 400 INVALID_DOCUMENTS. Nothing is written. details lists up to 20 problems, each with docId, path and message. Fix them and send the batch again.
Fields the mapping does not list are accepted and stored, but search ignores them. They show up as unmapped fields on Fields & mapping.
Limits
- Up to 1000 documents per request.
- Up to 10 MB of JSON per request. A larger body answers
413 PAYLOAD_TOO_LARGE. - A document
idis 1 to 512 bytes.
To send more through the API, split the catalog into batches and send them one after another. Each batch is its own task. The cabinet upload does the splitting for you.
Upload a file in the cabinet
On Documents, drop a .json file on Upload documents or click the area to choose one. The file holds a JSON array of documents, or an object with a documents array:
[
{ "id": "tape-48", "title": "Brown packaging tape 48mm", "color": "Brown", "price": 1.86, "categories": ["Tapes"] },
{ "id": "box-300", "title": "Cardboard box 300x200x200mm", "color": "Brown", "price": 0.32, "categories": ["Boxes"] }
]
The cabinet splits the file into batches of up to 1000 documents and 10 MB, sends them one after another and shows a row for each batch's task as it runs.
- A batch the API refuses shows its error code and the documents at fault. The other batches still go through.
- Each batch carries an Idempotency-Key made from the file's content and the batch number. After a failure, upload the same file again: batches already accepted in the last 24 hours are not written twice.
Browse documents
Open Documents. The table lists the stored documents in id order, 20 at a time; click Load more for the next ones. Type an id or a value in the search box above the table to narrow it. View JSON shows a stored document as it is saved, unmapped fields included. Refresh loads the list again.
The search box matches a document id exactly, or the start of a value in a searchable or filterable field.
With the API:
GET/v1/projects/:projectId/indices/:indexId/documentsProject member or any secret keyGET/v1/projects/:projectId/indices/:indexId/documents/:docIdProject member or any secret keyThe list returns documents ordered by id, as search sees them. limit is 1 to 100 (default 20). Pass nextCursor as cursor for the next page. q does what the search box does. One document by id answers 404 DOCUMENT_NOT_FOUND when the index does not hold it. Over MCP, the tool is get_document.
Delete documents
Open Documents and tick the documents in the table. Click Delete selected at the top of the page. The button shows how many are ticked. There is no confirmation step. The page shows the delete task's status and reloads the list when the task ends.
The body is a bare JSON array of up to 1000 document ids.
curl -X POST https://api.search.zaproo.cloud/v1/projects/$PROJECT_ID/indices/$INDEX_ID/documents/delete \
-H "Authorization: Bearer $ZS_KEY" -H "Content-Type: application/json" \
-d '["tape-48", "box-300"]'
# 202 {"taskId": "..."}
Delete the documents tape-48 and box-300 from the products index.
The agent calls delete_documents.
/v1/projects/:projectId/indices/:indexId/documents/deleteProject admin or a full-access secret keyAn id the index does not hold is not an error. Duplicate ids are counted once.
Delete all documents
Deleting all documents (a flush) empties the index at once, to start its data over after a bad import, test data or a catalog switch. It cannot be undone.
- Open Documents and scroll to the Danger zone at the bottom.
- Click Delete all documents. The dialog lists what is deleted and what stays, and warns you when an enabled data source would fill the index again.
- Type the index name and click Delete all documents.
The page then shows the task until it ends, and how many documents it deleted. Members see the button disabled.
curl -X POST https://api.search.zaproo.cloud/v1/projects/$PROJECT_ID/indices/$INDEX_ID/documents/flush \
-H "Authorization: Bearer $ZS_KEY"
{ "taskId": "5c2d1f0a-7e43-4b8e-a1d9-3f6b2c8e0d47", "previousDocCount": 1200 }
Delete every document in the products index.
The agent calls flush_documents. It should ask you to confirm first.
/v1/projects/:projectId/indices/:indexId/documents/flushProject admin or a full-access secret keyThe answer is 202 with a DOCS_FLUSH task. previousDocCount is the number of documents the index held. When the task is SUCCEEDED:
- Every physical version of the index is deleted, the one search served included. The index serves a new, empty version built from the saved mapping, so it is
READY(a pendingNEEDS_REINDEXis resolved) anddocCountis 0. - Search answers
200with no hits. Autocomplete stops suggesting values of the deleted documents once its suggestions are rebuilt, which the flush starts. Custom suggestions and popular searches stay. - The mapping, facets, search profiles, rules, synonyms, autocomplete settings, data source, analytics and API keys stay.
- The documents quota on the Billing page follows at its next count.
What was in flight when the flush was accepted:
- Uploads and deletes still
QUEUEDareCANCELLED. A write already running finishes first, and what it wrote is deleted too. - An open replace session is aborted and a running reindex is cancelled.
- Uploads sent after the flush land in the new version, even while the task runs.
- An enabled data source fills the index again at its next sync. Switch it off on the Data source page first to keep the index empty.
A second flush while one is queued or running answers the same task. Cancelling a reindex while a flush runs answers 409 FLUSH_RUNNING. If the saved mapping would break a profile, rule, facet, autocomplete field or data source, the flush is refused with 409 MAPPING_BREAKS_CONFIG and nothing is stopped, as for a reindex.
Follow a task
Open the index, then Tasks. The table lists the index's tasks, newest first, with each task's type, status, counts, creation time and duration. Pick a status in the Status list to see only those tasks, and click Refresh to load new ones. Details opens a task with its attempts, counts and errors, one row per failed document.

Every write is a task with its own counts
Documents are searchable once their task reaches SUCCEEDED or PARTIAL, and the index's document count includes them by then.
With the API, the write answers 202 with the task id. Poll it every second or two until the status is one of SUCCEEDED, PARTIAL, FAILED or CANCELLED:
/v1/projects/:projectId/tasks/:taskIdProject member or any secret keyGET/v1/projects/:projectId/tasksProject member or any secret keycurl https://api.search.zaproo.cloud/v1/projects/$PROJECT_ID/tasks/$TASK_ID \
-H "Authorization: Bearer $ZS_KEY"
{
"id": "0b9f7c1e-3a52-4f7e-9d0b-6a1c2e4d8f10",
"type": "DOCS_UPSERT",
"status": "PARTIAL",
"seq": 1042,
"attempts": 1,
"counts": { "received": 2, "succeeded": 1, "superseded": 0, "failed": 1 },
"errors": [{ "docId": "box-300", "type": "mapper_parsing_exception", "reason": "failed to parse field [price]" }],
"idempotencyKey": "catalog-2026-10-16-batch-1",
"sessionId": null,
"createdAt": "2026-10-16T09:30:00.000Z",
"startedAt": "2026-10-16T09:30:00.120Z",
"finishedAt": "2026-10-16T09:30:00.480Z"
}
The list returns the project's tasks newest first. Filter with indexId and status. limit is 1 to 100 (default 50). Pass nextCursor from the previous page as cursor to get the next page. Over MCP, the tool is get_task.
Task status
| Status | Meaning |
|---|---|
QUEUED | Accepted, waiting for a worker. A task that hit a temporary error waits here before its next attempt. |
RUNNING | A worker is writing it. |
SUCCEEDED | Every document was written or superseded. |
PARTIAL | Some documents failed. errors names them. |
FAILED | Every document failed, or the task failed as a whole. |
CANCELLED | The index was deleted or flushed, or the reindex or replace session the task belonged to was cancelled, aborted or expired. |
Document tasks are tried up to 5 times, with a growing pause, when the search cluster is busy or unreachable. attempts counts the tries. A document the cluster rejects for its content is not retried: it lands in errors and the task ends PARTIAL or FAILED.
Task types
| Type | Created by |
|---|---|
DOCS_UPSERT | An upload |
DOCS_DELETE | A delete |
SESSION_BATCH | A batch sent to a replace session |
SESSION_COMMIT | A replace session commit |
REINDEX | A reindex |
SUGGEST_BUILD | A rebuild of the autocomplete suggestions |
DOCS_FLUSH | A delete of every document |
Counts and errors
received: documents in the batch. For a reindex, the documents to copy. For a flush, the documents the index held.succeeded: documents written.superseded: documents skipped because a newer version was already stored.failed: documents the cluster rejected.
errors holds up to 1000 entries, each with docId, type and reason. docId is null for an error that failed the whole task, and Details shows it as whole task.
Retry safely with Idempotency-Key
This applies to API writes. A network error leaves you unsure whether the API received a batch. Send an Idempotency-Key header with every write, up to 200 characters, and retry with the same key.
- The same key within 24 hours returns the task of the first request. No second task is created, even if the body changed.
- Keys are unique per project. The same key for another index, another replace session or another operation answers
409 IDEMPOTENCY_KEY_REUSED. - After 24 hours the key can be used again.
Build the key from what makes the batch unique, for example your export run and the batch number. The cabinet upload does this with a hash of the file and the batch position, so uploading the same file twice does not write it twice.
Write order and superseded documents
Each task gets a sequence number, seq, when the API accepts it. The worker writes every document with seq as its version, and the index never replaces a document with an older version. When two tasks change the same document, the one accepted last wins, whichever finishes first.
A write that finds a newer version already stored is counted as superseded. It counts as success: the newer data is already there. You see superseded documents when an older task was retried after a newer one, or when two of your processes send the same product.
Common mistakes
- Sending
{"documents": [...]}to the API. The API takes a bare array. Only the cabinet upload accepts the wrapped form. - Sending only the changed fields. An upsert replaces the whole document.
- Treating
202as done. The documents are searchable only when the task finishes. - Retrying without an Idempotency-Key. A retry of a batch the API already accepted creates a second task. It writes the same data, but it costs a task and shows twice in the task list.
Related
Mapping and fields
Field types, the five flags that decide what a field is used for, text analysis options, unmapped fields, and which changes need a reindex.
Full catalog sync
Replace the whole catalog with a replace session, so products missing from your export disappear from search, without downtime.