Seller journeysBulk operations & jobs

Bulk operations & jobs

Four endpoints accept batches. Below the sync cap they answer inline; above it — or whenever you ask — they return a job you poll. Always handle both shapes: the same endpoint can do either.

EndpointSync capAsync capJob operation
POST /products/bulk505,000product_create
POST /inventory/bulk-update 1005,000inventory_update
POST /orders/bulk-ship 501,000order_ship
POST /orders/bulk-cancel501,000order_cancel

The job mechanism below applies to all four bulk operations. Choose sync or async per batch size and retry requirements.

Choosing sync or async

A request runs asynchronously when either of these is true:

  • The batch is larger than the sync cap.
  • You send Prefer: respond-async — this works at any size, so you can opt into the job flow even for a two-item batch.

Send an Idempotency-Key on every bulk submission. It is optional on POST /products/bulk and POST /inventory/bulk-update but strongly recommended: it lets a retried submission replay the original job instead of creating a duplicate batch. Reusing a key with a different batch returns 422 IDEMPOTENCY_KEY_REUSED. Exceeding the async cap returns 400 VALIDATION_ERROR.

The async submission response

Async submissions return HTTP 202 with two headers that tell you where and when to poll:

HTTP/1.1 202 Accepted
Location: /open/v1/jobs/job_01HXYZ…
Retry-After: 5

{
  "success": true,
  "data": {
    "job_id":   "job_01HXYZ…",
    "status":   "pending",
    "location": "/open/v1/jobs/job_01HXYZ…"
  }
}

Follow Location and wait Retry-After seconds before the first poll. A response that is not 202 means your batch ran synchronously — read the inline result instead.

Polling a job

GET/open/v1/jobs/{jobId}scope derived from the operation
{
  "success": true,
  "data": {
    "job_id":    "job_01HXYZ…",
    "operation": "product_create",
    "status":    "partial_success",
    "summary": {
      "total":           500,
      "succeeded":       486,
      "failed":          12,
      "outcome_unknown": 2
    },
    "items": [
      { "index": 0,  "status": "succeeded",
        "resource_id": "prod_abc123" },
      { "index": 14, "status": "failed",
        "error": { "code": "CATEGORY_NOT_FOUND", "message": "Category does not exist" } }
    ],
    "pagination": { "offset": 0, "limit": 100 }
  }
}

index is the position in the array you submitted, so results map back to your input. resource_id appears on successful creates; result carries an operation-specific payload where one exists; error appears on failures.

Job status values

StatusTerminalMeaning
pendingnoAccepted, not yet started
processingnoRunning. Keep polling
completedyesEvery item succeeded
partial_successyesSome items failed. Read items — a 200 here does not mean your batch worked
failedyesEvery item failed, or the job could not run

Item status values

Individual entries in items use a different set from the job as a whole. Do not reuse one enum for both.

Item statusMeaning
pendingNot yet attempted
processingIn flight
succeededApplied. resource_id is present for creates
failedRejected. error.code tells you why
outcome_unknownSubmitted, result unconfirmed — see below

outcome_unknown needs explicit handling. These items were submitted but the platform could not confirm whether they took effect — typically a timeout while the write was being confirmed. They are neither successes nor failures. Reconcile each by reading the resource directly before retrying, or you risk duplicating it.

Reading results

items is paginated via offset and limit (maximum 100). For a large batch, page until you have read summary.total entries. Items are indexed against the order you submitted them, so index maps back to your original array.

Jobs run to completion. There is no cancel endpoint — once submitted, a job progresses to a terminal state on its own. Poll it to read the outcome, and use item-level results to decide what to retry.

Polling guidance

  • Wait the Retry-After value from the 202 (currently 5 seconds) before the first poll, then back off to a 5–10 second interval. Job polls draw on your rate limit like any other call.
  • Stop when status is terminal. Do not poll on a fixed timer forever.
  • Persist the job_id at submission. It is the only way to recover the outcome if your process restarts.