Idempotency

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

POST /notes and POST /contacts 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
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 replay

POST /contacts follows the same rule. A duplicate created through a matching idempotency key replays. A different key whose name or email matches an existing contact under the same parent can return 409 duplicate_contact.

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 approved source event and choose a new stable key.