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 creates, supplier ship-from creates, product creates, and product-family creates 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 body returns 409 idempotency_key_reused.

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

Retry rule

On network failure or 429 rate_limited, retry the same POST with the same idempotency key and same body. If a caller needs to change the body, treat it as a new source event and choose a new stable key.