Idempotency
POST /orders moves money, so it is the one endpoint where a retry must never
mean a second payment. That is what X-Idempotency-Key is for, and it is
mandatory — not a nice-to-have.
X-Idempotency-Key: 0190f9b2-3c4d-4e5f-8a6b-7c8d9e0f1a2b
Omit it and the request never reaches the order logic:
{
"message": "\"Missing required request parameters: [X-Idempotency-Key]\"",
"requestId": "92885917-5fb9-41d1-8272-70cfdefe6c23"
}
That body comes from the API gateway, not from the service: the header is declared
required at the edge, so the request is turned away before any order logic runs.
It is the one error shape with a requestId and no statusCode — quote that id if
you open a ticket about it.
It is also your reference​
The key is an opaque, client-generated string — format is up to you, and we
forward it untouched. It comes back in the order as idempotency_key, which
makes it the handle you use to find that order later:
GET /orders?idempotency_key=0190f9b2-3c4d-4e5f-8a6b-7c8d9e0f1a2b
So pick something meaningful and unique in your system — an invoice id, a payment run id, a UUID you store next to the payable. If you generate a random key and throw it away, you have thrown away your own reference.
What happens on a repeat​
| You send | Result |
|---|---|
| Same key, same request | 201 with the original order. Nothing new is created. |
| Same key, different request | 409 B2B_IDEMPOTENCY_CONFLICT |
| New key | A new order |
That first row is the behaviour you want after a timeout, a dropped connection, or any response you could not read. Replay the identical request with the identical key and you will either create the order (if the first attempt never landed) or receive the one you already have.
The second row is a bug signal, not something to work around: the same key arrived with different data. Do not retry it with a new key until you know which of the two payloads was the one you meant.
The other single-use thing​
Idempotency keys are not the only uniqueness rule on POST /orders, and the
other one behaves differently:
{
"code": "B2B_QUOTATION_ALREADY_USED",
"category": "PERMANENT",
"message": "La cotización referenciada ya respalda otra operación: es de uso único. NO reintente con la misma cotización. Verifique el estado de la operación existente antes de crear una nueva; si corresponde, genere una cotización nueva."
}
A quotation is single-use: it backs exactly one order, forever, regardless of how that order ended. So:
- Never retry with the same
quotation_id. It fails identically every time. - Before creating a replacement order, look up the existing one. There is already an operation in flight or completed against that quote, and creating a second one is how you double-pay a beneficiary.
A new attempt means a new quotation — which also means a fresh rate, since quotes expire.
No other endpoint takes it​
X-Idempotency-Key only applies to POST /orders. Reads are naturally
idempotent, and POST /quotations is safe to repeat: an extra quotation costs
nothing and commits nothing until an order references it.