> ## Documentation Index
> Fetch the complete documentation index at: https://docs.connectadesk.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Idempotency

> Use the Idempotency-Key header to repeat POST and PUT on the Connecta DESK API without running the same operation twice.

When a request times out, you do not know whether it reached the API. If you repeat it carelessly,
the resource may be created twice. On `POST /messages`, the end customer receives the same message
twice.

With the `Idempotency-Key` header, the first call runs and the repetitions get the same response
as the first time.

<ParamField header="Idempotency-Key" type="string">
  Unique key for the operation, up to 200 characters. Accepted on `POST` and `PUT`. It is optional:
  without it, each call runs normally.
</ParamField>

## How to use it

Generate a unique identifier for each business operation and use that same identifier on every
attempt of that operation. With the same key, the API understands that the attempts are a single
operation.

<CodeGroup>
  ```bash cURL theme={null}
  curl --request POST \
    --url "$CONNECTA_URL/messages" \
    --header "Authorization: Bearer $CONNECTA_TOKEN" \
    --header "Content-Type: application/json" \
    --header "Idempotency-Key: order-88213-confirmation" \
    --data '{
      "channelId": 4,
      "number": "5511999999999",
      "body": "Your order #88213 has been confirmed."
    }'
  ```

  ```javascript Node.js theme={null}
  async function sendWithRetry(payload, key, attempts = 3) {
    for (let i = 1; i <= attempts; i++) {
      try {
        const res = await fetch(`${process.env.CONNECTA_URL}/messages`, {
          method: 'POST',
          headers: {
            Authorization: `Bearer ${process.env.CONNECTA_TOKEN}`,
            'Content-Type': 'application/json',
            'Idempotency-Key': key
          },
          body: JSON.stringify(payload)
        });

        if (res.ok) return res.json();
        if (res.status < 500) throw new Error(`Final error: ${res.status}`);
      } catch (err) {
        if (i === attempts) throw err;
      }

      await new Promise((r) => setTimeout(r, 2 ** i * 500));
    }
  }

  // The key comes from the domain, not from a random value per attempt.
  await sendWithRetry(payload, `order-${order.id}-confirmation`);
  ```

  ```python Python theme={null}
  import os, time, requests

  def send_with_retry(payload, key, attempts=3):
      for i in range(1, attempts + 1):
          try:
              res = requests.post(
                  f"{os.environ['CONNECTA_URL']}/messages",
                  json=payload,
                  headers={
                      "Authorization": f"Bearer {os.environ['CONNECTA_TOKEN']}",
                      "Idempotency-Key": key,
                  },
                  timeout=10,
              )
              if res.ok:
                  return res.json()
              if res.status_code < 500:
                  res.raise_for_status()
          except requests.RequestException:
              if i == attempts:
                  raise

          time.sleep(2**i * 0.5)

  send_with_retry(payload, f"order-{order_id}-confirmation")
  ```
</CodeGroup>

<Tip>
  The best keys come from something that is already unique in your system, such as
  `order-88213-confirmation`, `invoice-2026-09-reminder` or `crm-card-771-note`. A random UUID works
  too, as long as you **save** it before the first attempt. If a new UUID is generated on each
  attempt, the key does not protect against duplicates.
</Tip>

## What happens in each case

<AccordionGroup>
  <Accordion title="Repetition with the same body" icon="repeat">
    The API returns the response from the first run, with the same status, and adds this header:

    ```
    Idempotent-Replay: true
    ```

    Nothing runs again. This is what happens on a retry after a timeout.
  </Accordion>

  <Accordion title="Same key, different body" icon="triangle-alert">
    ```json theme={null}
    {
      "error": {
        "code": "ERR_API_IDEMPOTENCY_KEY_REUSED",
        "message": "Idempotency-Key já usada com um corpo diferente."
      }
    }
    ```

    The API refuses the request with `409`. Using the same key for another operation almost always
    points to a mistake in how your integration generates keys.
  </Accordion>

  <Accordion title="The first one is still in progress" icon="hourglass">
    ```json theme={null}
    {
      "error": {
        "code": "ERR_API_IDEMPOTENCY_IN_PROGRESS",
        "message": "Uma requisição com esta Idempotency-Key ainda está em processamento."
      }
    }
    ```

    The response is also `409`. It happens when two attempts run at the same time. Wait and check
    the result instead of sending a third attempt.
  </Accordion>

  <Accordion title="Key too long" icon="ruler">
    With more than 200 characters, the request is refused with `400 ERR_API_IDEMPOTENCY_KEY_INVALID`
    before anything runs.
  </Accordion>
</AccordionGroup>

## Key rules

**Errors are not stored.** Only successful responses are recorded. If the call failed because of
invalid data, fix the body and repeat with the same key, since the operation has not happened
yet.

**The key applies per token, method and route.** The same key used on `POST /messages` and on
`POST /contacts` counts as two different operations, with no conflict. Different tokens do not share
a key either, even within the same company.

**Responses without a body are replayed too.** Operations that answer `204`, such as applying a tag
to a ticket, record the result like any other, and the repeat returns `204` with
`Idempotent-Replay: true`.

**Uploads do not use the key.** With `multipart/form-data` the file has not been read yet when the
check happens, so there is no body to compare and the key is ignored. This covers media sending,
campaigns with a file and scheduled message attachments: handle duplicates on your side there.

**The key expires.** Keys are kept for 24 hours (this period can be configured on the
installation). After that, the same key makes the operation run again. The protection is meant for
retries made right after a failure. A resend made weeks later is treated as a new operation.

<Warning>
  If there is an internal instability, the API processes the request normally, without the
  idempotency protection, instead of refusing it. For this reason, critical operations should also
  be able to detect duplicates on your side.
</Warning>

## Where to use it

<Columns cols={2}>
  <Card title="POST /messages" icon="send" href="/en/guides/enviar-mensagens">
    This is where a duplicate is visible to the end customer. Always use it.
  </Card>

  <Card title="POST /campaigns" icon="megaphone">
    A campaign created twice sends to your entire base twice.
  </Card>

  <Card title="POST /contacts" icon="user-plus">
    Avoids a duplicate record when the retry comes from an automated routine. See also
    `POST /contacts/upsert`.
  </Card>

  <Card title="POST /crm/cards" icon="kanban">
    Repeated cards clutter the CRM board and distort the metrics.
  </Card>
</Columns>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.