Skip to main content
A write whose response you did not receive leaves a question: did it run? An idempotency key answers it on the operations that honor the key. Eight operations read the key. Seven of them replay a repeated request, in three different ways, and POST /v1/chats/{chat_id}/share-contact-card refuses a repeat once the card’s message exists. Every other operation ignores the header. Read this page before you write a retry. The key travels in the Idempotency-Key request header. X-Idempotency-Key is read when the first is absent. The key is a string of your choice: use a random UUID.

Operations that honor the key

POST /v1/chats/{chat_id}/share-contact-card reads the key and never replays. Once a request has created the card’s message, a repeat with the same key returns 409 idempotency_conflict. When no message was created, the repeat sends the card. See Contact cards.

Lines and hosted macOS sessions

POST /v1/lines/assign, POST /v1/lines/shared and POST /v1/macos-sessions/rent each take or create something billable. Without a key, every retry takes one more line or rents one more session. Always send a key to these three operations.
  • Replay. A request repeated with the same key, the same method, the same path and the same body within 24 hours returns the first successful response again. The body is the first one, with the trace_id and the request_id of the first request. The X-Request-Id header belongs to the new request, so it differs from request_id in the body. No header marks a replay.
  • Same body. The body is compared byte for byte. A request with no body and a request with the body {} are two different requests.
  • Collision. The same key with another method, path or body returns 409 idempotency_key_collision.
  • In flight. While the first request is still running, a repeat returns 503 idempotency_in_flight: retry with the same key. A request that was abandoned midway frees its key after 60 seconds.
  • Failures. Only a 2xx response is recorded. A request that failed leaves no trace: a retry with the same key runs again.
  • Lifetime. The key is remembered for 24 hours from the request that succeeded. After that, the same request with the same key runs again: it takes or creates another line, or rents another session.
  • Length. A key longer than 255 characters returns 400 validation_failed, with an issue on idempotency-key and the code too_big.
One case does not replay. POST /v1/lines/assign with as_test_line: true assigns the line first and converts it second. When the conversion alone fails, the response is the error, the line stays assigned to you as a rented line, and the key is not recorded: a retry with the same key assigns another line. Before you retry, read your lines with GET /v1/lines/my.

Messages, reactions and contact cards

POST /v1/messages and the two reaction operations recognize a request repeated with the same key on the same line and with the same content. The key belongs to the line: on a send, the line that sends the message, and on a reaction, the line of the message.
  • Replay. A repeated send returns 202 with the message already created, in its state at the time of the repeat: status can have moved to sent or delivered, and queue_info is null. A repeated POST of a reaction returns the entry of the first call as it is now, and does not add the reaction again: after a later DELETE, the repeat returns that entry although the reaction is removed. Read the reactions field of the message to know what is in place. When the first call added the reaction, the repeat returns 409 idempotency_conflict once a later call has changed its type. When the first call changed the type of an existing reaction, or found that type in place, iSnap does not record its key: a repeat after a later change of type changes the reaction again. A repeated DELETE returns the response of the first call. The trace_id and the request_id are new. Apart from that case, nothing is sent a second time.
  • Checks before the key. On a send, the checks on the line, and with a trial key the checks on the trial, run before the key is read. A repeat returns 400 line_not_active or 403 line_suspended once the line is no longer active (403 line_not_active with a trial key), and 402 trial_needs_inbound once the trial is dormant, which the last send a trial accepts makes it. Such a refusal does not mean that the first request was refused: look for the message with GET /v1/messages.
  • Same content. The content is compared after parsing: the order of the JSON fields does not matter.
  • Collision. The same key on the same line with different content returns 409 idempotency_conflict. The same key with another line_id is a separate request: nothing is compared, and a new message is created. A reaction and a send share the keys of their line. A POST /v1/messages with the key of a reaction is not refused: it returns 202 with the reaction entry and sends nothing. Use a new key for every action.
  • Lifetime. On a send, the key is remembered as long as its message exists: at least 72 hours from the creation of the message. Once the message is deleted, the same key creates a new message. On a reaction, the key is remembered at most until the message reacted to is deleted: the reaction entry is deleted with that message, which can be less than 72 hours after the reaction. The reaction call then returns 404 not_found, and the key can be used again on the line.
  • One at a time. Send retries one after the other, not in parallel, and keep the key at 255 characters or fewer. Two simultaneous requests with one new key, or a longer key, can end in 500 internal_error. Two requests with one key never create two messages: one of them can end in 500 internal_error, and a retry with the same key then returns the message.
  • Trial keys. With a trial key, the key belongs to the trial’s pool line, which other trials can share. Use keys that are unique everywhere, such as random UUIDs.
A repeat of POST /v1/chats/{chat_id}/share-contact-card with the same key is not replayed: it returns 409 idempotency_conflict once the card’s message exists, and sends the card when no message was created. The key belongs to the line of the chat, and is remembered as long as the card’s message exists, at least 72 hours from its creation: once that message is deleted, a repeat sends the card. Send a key with every call: without a key, every repeat sends the card. A key that a reaction already used on the chat’s line is not refused: the call returns 202 with the id of the reaction entry in message_id, and sends nothing. Use a new key for every call. When you did not receive the response of this call, look for the message with GET /v1/messages before you resend the card.

Attachments

On POST /v1/attachments, a repeat with the same key returns the same attachment id with a new upload_url and a new upload deadline, as long as the first attachment is still awaiting_upload and its deadline has not passed. The deadline is 15 minutes after the call, and each repeat that returns the attachment sets a new one. The body of the repeat is not compared with the first. In any other state, the key returns 409 idempotency_terminal_state: create the attachment again with a new key. The key is never forgotten: once it has created an attachment, it never creates another one, and the 409 has no time limit. See Attachments.

Retrying safely

Retry a write only on an operation that honors Idempotency-Key, with the key of the first attempt.
  1. Generate the key before the first attempt, one per action, and store it with your own record of the action: the message to send, the line to assign.
  2. Send the request. When the response does not arrive, or is a 500 or a 503, resend the same request with the same key, up to 2 times, with exponential backoff.
  3. Send retries one after the other. Never send two requests with the same key at the same time.
  4. To send a new message after a failed one, use a new key: iSnap never resends a failed message, and the old key returns the failed message.
  5. Retry while the key is remembered: within 24 hours of the first attempt on the three line and session operations, within 72 hours on a send, and while the message reacted to exists on a reaction. Later, the key can be forgotten, and the same request to a line or session operation, or the same send, then runs again: see the row “How long the key is remembered” of Operations that honor the key.
What each response to a keyed request means:
The samples import the helpers of Authentication.

Operations that ignore the key

Every operation that Operations that honor the key does not list ignores the header. A repeated request acts again: When you did not receive the response of one of these writes, do not resend it blindly. Read the resource first: list your webhook subscriptions, your pre-orders or your lines, and resend the write only when the first one did not run. A repeat of a write that already succeeded can return an error instead of acting twice: a second cancel of a message returns 409 too_late_to_cancel, a second withdrawal of a variant change 404 no_pending_variant_change, a second release 404 not_found, and a second cancel of a pre-order 400 pre_order_already_confirmed. Read the resource before you report a failure. Some writes are safe to repeat because the second request finds the work done. POST /v1/lines/{id}/terminate on a line that is already cancelled returns 200 with the line unchanged. POST /v1/lines/{id}/cancel on a line that already has a cancel_at returns the same cancel_at. The guide of each operation says so where it holds. By default, the SDK adds an Idempotency-Key to every write. It retries every method, the ones of this section included. Pass { maxRetries: 0 } to a method whose operation ignores the key: see SDK errors. A client created with idempotencyKey: "manual" sends a key only when the call passes one, and still retries.