Idempotency
A network drops, a timeout fires, you never get the response. Do you retry? With an Idempotency-Key, yes, safely: the same operation is not done twice nor charged twice. Today it is honored by the short link creation. It is optional; if you do not send the key, everything works as before.
5 min read
What it is
Idempotency guarantees that repeating a request has the same effect as making it once. You generate a unique identifier per operation and send it in the Idempotency-Key header. If your organization already created a link with that key, the API returns that link instead of creating another one, and does not charge it again.
The key does not expire: it is stored with the link it created, forever, and it is unique within your organization. Retrying a minute later or a month later is equally safe. The other side: a used key cannot serve another link, not even months later.
Think of the key as "one per business intent", not "one per retry". Retrying the same send reuses the same key. Two different sends carry two different keys.
How to use it
- Generate a unique identifier per operation. We recommend a UUID v4.
- Send it in the request
Idempotency-Keyheader. - If the operation fails on the network, times out or gets a
5xx, retry it with the same key and the same body. - For a different operation, use a new key.
The key accepts 1 to 255 characters from the set A-Z a-z 0-9 _ -. Scope is per organization: your key never collides with another account. An empty header is the same as not sending it.
Examples
You only add one header. The body and the rest of the request stay the same.
curl -X POST https://developers.hablame.co/api/v6/urlshortener/links \
-H "Authorization: Bearer hk_TU_API_KEY" \
-H "Idempotency-Key: 5f3b2c10-9a7e-4b2d-8c1f-0a1b2c3d4e5f" \
-H "Content-Type: application/json" \
-d '{"longUrl":"https://hablame.co/promo"}'Responses
The API tells you what happened with each key in the Idempotency-Status header.
New: it ran
The first time. The link is created, charged (unless your plan includes it) and the key is stored with it.
HTTP/1.1 201 Created
Idempotency-Status: created
{ "success": true, "data": { "domain": "h0b.co", "code": "a1b2c3d", "shortUrl": "https://h0b.co/a1b2c3d", "status": "active" } }Repeated: replay
A retry with the same key and the same body. No other link is created, nothing is charged again and it does not use your plan’s daily quota: you get the same 201 with the link that already exists. It comes as it is today: if you edited it later, with its new destination; if you deleted it, with status: deleted. It counts as the same body even if the alias casing, the spaces around the destination or the time zone you write the same expiry in change.
HTTP/1.1 201 Created
Idempotency-Status: replayed
Idempotency-Replayed: true
{ "success": true, "data": { "domain": "h0b.co", "code": "a1b2c3d", "shortUrl": "https://h0b.co/a1b2c3d", "status": "active" } }Two at once: a single link
If you send the same key while the first request is still being processed —for example, because your client got tired of waiting and retried—, you do not get an error and you do not have to wait: both end with the same link. Only one is created and charged, and the one that arrives second responds Idempotency-Status: replayed.
Reused with a different body: 422
You used a key your organization already used, with a different body. Nothing is created or charged. It is almost always a client bug: one key identifies one operation, not several. Use a new key.
HTTP/1.1 422 Unprocessable Entity
{ "success": false, "error": { "code": "IDEMPOTENCY_KEY_REUSED" } }Invalid key: 400
The key is too long or contains characters outside A-Z a-z 0-9 _ -. Nothing is done.
HTTP/1.1 400 Bad Request
{ "success": false, "error": { "code": "IDEMPOTENCY_KEY_INVALID" } }Tips for automatic retries
- Generate the key before the first attempt. Store it and reuse it on every retry of the same event. Generating a new key per attempt loses the protection.
- On a timeout, a network error or a
5xx, retry with the same key and the same body: if the link got created, you receive it; if not, it is created now. In no case is it charged twice. - Treat a 422 as a bug on your side: you changed the body under the same key. Fix the logic, do not retry in a loop.
- Retries use the endpoint quota like any other request: use exponential backoff with random jitter, just like with usage limits.
The key is only stored when a link is created. If the first request failed with an error —an invalid value, insufficient balance, a taken alias—, nothing was created and the key is still free: the retry runs from scratch and, if the problem persists, gets the same error. Errors are not stored to be replayed.
Where it applies today
Today two endpoints honor it, both for short link creation: `POST /api/v6/urlshortener/links`, creating one link, and `POST /api/v6/urlshortener/links/batch`, bulk creation. In bulk creation the key belongs to the whole batch: when you repeat it, the links already created come back as replayed —without being created or charged again— and whatever was missing is tried again. On the other endpoints the header has no effect: a retry runs again and, if the operation is charged, it is charged again.