Indexing

Documents and tasks

Upload, browse and delete documents in the cabinet or through the API, follow each write as a task, and retry safely with Idempotency-Key.

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.

The Documents page with the Upload documents drop zone, a search box and a table of documents with ID, SKU, name, description and a View JSON buttonDrop 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.

POST/v1/projects/:projectId/indices/:indexId/documentsProject admin or a full-access secret key

Before 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 id is 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 key

The 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.

POST/v1/projects/:projectId/indices/:indexId/documents/deleteProject admin or a full-access secret key

An 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.

  1. Open Documents and scroll to the Danger zone at the bottom.
  2. 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.
  3. 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.

POST/v1/projects/:projectId/indices/:indexId/documents/flushProject admin or a full-access secret key

The 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 pending NEEDS_REINDEX is resolved) and docCount is 0.
  • Search answers 200 with 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 QUEUED are CANCELLED. 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.

The Tasks page with a Status filter and two SUCCEEDED tasks, a SUGGEST_BUILD and a DOCS_UPSERT, with received, succeeded, superseded and failed counts, created time and durationEvery 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:

GET/v1/projects/:projectId/tasks/:taskIdProject member or any secret keyGET/v1/projects/:projectId/tasksProject member or any secret key
curl 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

StatusMeaning
QUEUEDAccepted, waiting for a worker. A task that hit a temporary error waits here before its next attempt.
RUNNINGA worker is writing it.
SUCCEEDEDEvery document was written or superseded.
PARTIALSome documents failed. errors names them.
FAILEDEvery document failed, or the task failed as a whole.
CANCELLEDThe 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

TypeCreated by
DOCS_UPSERTAn upload
DOCS_DELETEA delete
SESSION_BATCHA batch sent to a replace session
SESSION_COMMITA replace session commit
REINDEXA reindex
SUGGEST_BUILDA rebuild of the autocomplete suggestions
DOCS_FLUSHA 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 202 as 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.