For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
my.pivotal.appGet an API key
Get StartedGuidesAPI ReferenceSDKsChangelog
Get StartedGuidesAPI ReferenceSDKsChangelog
  • Welcome
    • Introduction
    • Quickstart
    • Authentication
  • Concepts
    • Rate limits
    • Errors
    • Pagination
    • Versioning
    • Idempotency
LogoLogo
my.pivotal.appGet an API key
On this page
  • Key rules
  • What we cache
  • Body must match
  • In-flight collision
  • When you need a key
  • Picking keys
  • Response headers
Concepts

Idempotency

Safe retries on POST with the Idempotency-Key header.

|View as Markdown|Open in Claude|
Was this page helpful?
Edit this page
Previous

Versioning

Built with

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.

1POST /api/v1/customers HTTP/1.1
2Authorization: Bearer pivotal_…
3Content-Type: application/json
4Idempotency-Key: 9c4d…-aurora-2026-05-26
5
6{ "name": "Aurora Outfitters", "slug": "aurora", "status": "onboarding" }

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. POST without an Idempotency-Key works 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:

1{
2 "error": {
3 "type": "conflict",
4 "code": "idempotency_key_reused",
5 "message": "Idempotency-Key was used with a different request body."
6 }
7}

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:

1{
2 "error": {
3 "type": "conflict",
4 "code": "idempotency_key_in_flight",
5 "message": "Idempotency-Key request is already in flight. Retry shortly."
6 }
7}

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:

  • GET requests — 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:

  1. Caller-side UUIDv4. Generate a fresh UUID per logical operation, store it on your side, retry with the same UUID.
  2. Natural key plus context. customer-create-{external-id}-{epoch-day} is fine if you trust your external-id. Avoid plain {external-id} — you may legitimately want to recreate after a delete.
  3. 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:

1HTTP/1.1 201 Created
2Idempotent-Replay: true
3Content-Type: application/json

Log that header to confirm retries are doing what you expect.