Idempotent requests

How to retry a POST safely with an Idempotency-Key, which routes need one, and what Gravv returns when a key is reused.

An idempotency key lets you retry a write without running the same operation twice. The key tells Gravv that two requests are attempts at the same operation.

To make an idempotent request, generate a key and send it in the Idempotency-Key header. You can create keys any way you like, but Gravv recommends a UUIDv4 or another high-entropy random string to avoid collisions.

When an idempotency key is required

Every POST request requires an Idempotency-Key, except routes under /v1/webhooks/ and /v1/external-accounts/verify. The check runs before authentication. Gravv never deduplicates GET, PUT, PATCH and DELETE requests, so they don't need a key.

The key must be 1 to 255 printable ASCII characters. A missing or malformed key returns HTTP 400 with one of these messages:

  • missing idempotency key in request headers
  • idempotency key must be between 1 and 255 characters
  • idempotency key must contain only ASCII printable characters

Use a different key for each operation. Reuse a key only when retrying the same method, path, and payload:

curl --request POST \
     --url https://api.gravv.xyz/v1/customers \
     --header 'Api-Key: <API_KEY>' \
     --header 'Idempotency-Key: 7f3c2a1e-5b4d-4c6e-9f8a-2b1c0d9e8f70' \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "first_name": "john",
  "middle_name": "sample",
  "last_name": "doe",
  "email": "john.doe@example.com",
  "phone": "+14074219876",
  "type": "individual",
  "gender": "male",
  "date_of_birth": "1990-06-15",
  "external_id": "individual_12345",
  "address": {
    "address_line1": "1800 N Pole St",
    "address_line2": "Suite 202",
    "city": "Orlando",
    "postal_code": "32801",
    "state": "US-FL",
    "country": "US"
  }
}
'

Gravv scopes each key to your API key, the method and the path. The same key on a different path, or with a different API key, is a new request. After you replace your API key, a retry with an old idempotency key runs again.

Retry behavior

Gravv stores every response below HTTP 500 for 24 hours, and replays it for a retry with the same key:

Situation Result
Same key and identical payload after completion Gravv replays the stored status and body, with the header Idempotency-Replayed: true
Same key while the original request is running 409 with Retry-After: 1 and the code idempotency_in_flight
Same key with a different payload 422 with the code idempotency_key_reused
The first response was a 5xx Gravv stores nothing, so a retry with the same key runs again
The request body is over 1 MiB Gravv does not deduplicate the request, so every retry runs. Base64 encoding makes an upload of a file over about 770 KB that large. Check what the first call stored before you retry such an upload
Missing or invalid key 400 with one of the messages above
{
  "data": null,
  "error": {
    "code": "idempotency_in_flight",
    "message": "A request with the same Idempotency-Key is currently being processed"
  }
}

When you receive 409 idempotency_in_flight, wait for the number of seconds in Retry-After and retry with the same key and payload.

Follow these rules:

  • Gravv stores errors too. Gravv replays a 400, 401, 403, 404 or 429 for 24 hours. After you correct a rejected request, or after a rate limit, send it with a new key. That is safe, because the rejected request did nothing.
  • Gravv compares payloads byte for byte. Re-serializing the same JSON with a different key order or spacing counts as a different payload and returns 422. Store the exact body you sent, and resend it.
  • Gravv never stores some responses, such as a transfer's 409 with the code ERROR_CODE_TRANSFER_IN_PROGRESS. Retry those with the same key after Retry-After.
  • After a 5xx or a timeout, check before you retry a payment. The operation may have run, so look it up first. List transactions with the source account_id returns the newest first. Check those transactions for the client_reference you sent. Send the retry only if it is not there.
Was this page helpful?