Idempotency

Write requests require a stable Idempotency-Key so retrying callers do not create duplicates.

POST /notes, POST /contacts, POST /customers, POST /suppliers, parent-scoped customer location/address and document creates, supplier ship-from creates, product creates, product-family creates, Cost Book updates, and Price Book updates require Idempotency-Key. Repeating the same route plus the same key and same request body returns the original record. Reusing the same key with a different intended update returns a conflict.

Key format

Build the key from stable source facts.

<source-system>:<source-event-id>:<target-type>:<target-id>:<action>

Examples:

gmail:thread-18f0:customer:customer-id:create-note
gmail:thread-18f0:customer:customer-id:create-contact:jane@example.com
call:recording-77:supplier:supplier-id:create-note
crm:lead-1842:customer:create-profile
crm:site-742:customer:customer-id:create-location
intake:doc-913:create-product
intake:doc-913:create-product-family
intake:file-482:purchase-order:purchase-order-id:attach
quote:2026-0819-line-2:price-book:customer-id:update
Warning

Do not use timestamps or random ids unless the source event is genuinely unique. A random key per retry disables replay protection.

Note replay

curl "$SHELFCYCLE_API_BASE_URL/notes" \
  -X POST \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: gmail:thread-18f0:customer:customer-id:create-note" \
  -d '{ "primarySubject": { "type": "customer", "id": "customer-id" }, "noteType": "EMAIL", "title": "Inbound thread", "body": "Approved summary." }'

First successful request:

{
  "data": {
    "id": "note-id",
    "type": "note",
    "idempotencyStatus": "created"
  }
}

Same key and same body:

{
  "data": {
    "id": "note-id",
    "type": "note",
    "idempotencyStatus": "replayed"
  }
}

Same key and different body:

{
  "error": {
    "type": "conflict_error",
    "code": "idempotency_key_reused",
    "message": "The Idempotency-Key was reused with a different request body.",
    "requestId": "request-id"
  }
}

Contact, profile, child-record, and product replay

POST /contacts, POST /customers, POST /suppliers, parent-scoped child creates, POST /products, and POST /product-families follow the same rule. A duplicate created through a matching idempotency key replays. A different key whose identity fields match an existing record can return 409 duplicate_contact, 409 duplicate_customer, 409 duplicate_supplier, 409 duplicate_customer_location, 409 duplicate_customer_address, 409 duplicate_supplier_ship_from_location, 409 duplicate_product, or 409 duplicate_family.

Price Book review and replay

Price Book uses the same stable key from POST /price-book-entries?dryRun=true through the reviewed execution. Add the preview's expectedAffectedEntryIds and expectedPlanToken to the execution body. If the execution response is lost, replay that exact execute request with the same key and body to recover the original saved entry.

If ShelfCycle reports that the reviewed plan is no longer current, run a new preview and review its affected entries before applying it. Keep the original key only for an identical replay of the original intended update.

Attached-document replay

Product, customer, supplier, and purchase-order document routes use the same replay rule. A newly uploaded file attachment returns data.integrity with the verified checksum, size, content type, and verification time. Replaying the exact route, key, and body returns the same stored integrity receipt without depending on a new file check.

Keep that receipt with the source event so a workflow can reconcile a lost response without creating another attachment. See Attached documents for the complete response.

Retry rule

On network failure or 429 rate_limited, retry the same POST with the same idempotency key and same body. Price Book execute retries must also keep the reviewed plan fields unchanged. If a caller changes the intended update, treat it as a new source event and choose a new stable key.