Idempotency
Safe retries on POST with the Idempotency-Key header.
Pivotal’s POST endpoints accept an Idempotency-Key header. Send the same key with the same body twice and you get the same response twice — the second request doesn’t create a duplicate resource.
This matters because networks fail. If a POST /customers request times out, you can’t tell whether the customer was created or not. With an idempotency key you can retry safely.
Key rules
- Format: any opaque string up to 255 bytes. UUIDs work; so do app-specific strings like
customer-create-aurora-2026-05-26. Whatever you pick has to be unique per logical operation. - Scope: keys are scoped to one workspace and one endpoint. Two different workspaces can use the same key without colliding. Two different endpoints with the same key behave independently.
- TTL: Pivotal retains idempotency results for 24 hours. After that, the same key is treated as fresh and will create a new resource.
- Header is optional.
POSTwithout anIdempotency-Keyworks exactly as before — at-most-once becomes at-least-once on retry.
What we cache
When you send Idempotency-Key and the request succeeds, Pivotal stashes:
- The HTTP status code.
- The full response body.
- A hash of the request body.
A second request with the same key returns the cached response, byte-for-byte, with the same id and display_id.
Body must match
If you send the same Idempotency-Key with a different body, Pivotal returns 409 Conflict:
This catches a common bug: the same key getting reused across two unrelated operations because someone hardcoded Idempotency-Key: 1.
In-flight collision
If two requests with the same key arrive concurrently, the second one returns 409:
Retry after a short delay and you’ll get the result of the first one (assuming it succeeded).
When you need a key
Use idempotency keys for:
- Customer creation — the main one. Don’t double-create from a “Sign up” button.
- Onboarding creation from a webhook (e.g. HubSpot closed-won) that may redeliver.
- Contact creation from any source that retries.
You don’t need one for:
GETrequests — already idempotent by definition.DELETE— soft delete twice on the same record is fine; the second call returns the same body.PATCH— same effect both times unless your patch is non-idempotent (rare with PATCH).
Picking keys
Three patterns work well, in order of preference:
- Caller-side UUIDv4. Generate a fresh UUID per logical operation, store it on your side, retry with the same UUID.
- Natural key plus context.
customer-create-{external-id}-{epoch-day}is fine if you trust yourexternal-id. Avoid plain{external-id}— you may legitimately want to recreate after a delete. - Request-body hash plus timestamp bucket.
sha256(body) + ":" + floor(now / 60s). Self-contained but slightly more code.
What doesn’t work: a global counter (collides on cold starts), the current Unix epoch (collides under concurrency), the user’s email (the same user might legitimately create multiple resources).
Response headers
Idempotent responses carry Idempotent-Replay: true on the second (cached) response:
Log that header to confirm retries are doing what you expect.