Skip to content

Idempotency

To prevent retries from causing duplicate card opens / duplicate charges, write endpoints require an Idempotency-Key header.

Which Endpoints Require It

EndpointRequired?
/api/v1/openapi/card/apply✅ Yes
/api/v1/openapi/card/recharge✅ Yes
All /list and /info endpoints❌ No
http
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
ConstraintValue
Length128 chars
CharsetURL-safe (UUID v4 / ULID recommended)
Dedup window24 hours
Missing400 openapi_idempotency_key_required
Too long400 openapi_idempotency_key_too_long
Bad characters400 openapi_idempotency_key_invalid_chars
Same key, different body409 openapi_idempotency_key_conflict

Server Behavior

Same key + same body

Returns a copy of the previous response (the original status code and data). If the first call is still processing, the call waits and returns the final result. Does not trigger a second card open.

The replayed response carries an extra response header so you can tell it apart from a fresh call:

http
X-Idempotent-Replay: true

Detecting replays

Treat the X-Idempotent-Replay: true header as informational. Either way the response body is the authoritative source of truth — but the header is useful for metrics and debugging ("this attempt actually hit the server but I retried after a network blip").

Only 2xx responses are cached

The server only caches the response body when the original call returned 2xx. If the first call returned 4xx (e.g. insufficient_balance) or 5xx, the same key + same body will be re-attempted on the next request — there's nothing to replay.

In other words, idempotency protects you against losing a successful response, not against repeating a failed business call.

Captured response size

The server caches at most 256 KB of the response body. The current OpenAPI responses are well under that ceiling, so this should never be a concern in practice — flagged only for completeness.

Same key + different body

Returns 409 openapi_idempotency_key_conflict. The server refuses to process. Client should switch keys or fix the body.

Different keys + same body

Treated as two independent requests — opens two cards / charges twice. By design — idempotency is keyed by the header, not the body.

Don't use a body hash as the key

Using "request body hash" as the Idempotency-Key would deduplicate "user clicks recharge twice for the same amount" into a single charge. The key represents one business intent; generate a new key for each new business intent.

Client SDK pattern

python
import uuid

def apply_card(header_id, package_id):
    key = str(uuid.uuid4())  # new key per business call
    return call("/api/v1/openapi/card/apply",
                {"header_id": header_id, "package_id": package_id},
                idempotency_key=key)

Async-task pattern

If your "apply card" task is retried by a job queue on network failure, bind the key to the task record:

python
def open_card_task(task_id, header_id, package_id):
    # All retries of the same task use the same key
    key = f"openapi:apply:{task_id}"
    return call("/api/v1/openapi/card/apply", {...}, idempotency_key=key)

That way, even if the task is retried N times, Coinepay opens at most one card.

Relationship with Signatures

Idempotency-Key is not part of the signing input (the signature covers method/path/query/timestamp/nonce/body sha256). Each request still needs a fresh nonce. They are two independent mechanisms:

MechanismProtects against
NonceAn attacker capturing a signature and replaying the request
Idempotency-KeyYou safely retrying after a network failure

Coordinating both: when the client retries the same business call, use the same Idempotency-Key + new Nonce + new Timestamp + new Signature.

Error Responses

Missing (400)

json
{
  "code": 400,
  "message": "Missing Idempotency-Key header",
  "message_key": "openapi_idempotency_key_required",
  "data": null
}

Conflict (409)

json
{
  "code": 409,
  "message": "Idempotency-Key conflict",
  "message_key": "openapi_idempotency_key_conflict",
  "data": null
}

Released under MIT-equivalent terms.