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

# Idempotency

> There is no Idempotency-Key header. Here's what that actually means for each verb.

No client-supplied request ID dedupes a retried write. If you have built against Stripe or a similar API and expect to hand a UUID to a `POST` so you can safely retry it, that mechanism does not exist here.

What follows is what is naturally safe to retry without one, and what is not.

## Safe to retry, by construction

<AccordionGroup>
  <Accordion title="GET — always safe" icon="download">
    Reads don't change state. Retry them as many times as you want, on any schedule.
  </Accordion>

  <Accordion title="PATCH / PUT — safe in practice" icon="pencil">
    Updates in this API are whole-field replacements, not deltas expressed as operations ("increment by one"). Sending the same `PATCH` body twice produces the same end state both times. The real risk isn't the retry itself — it's two different requests racing on the same resource, where the second write wins regardless of which one you intended to land last. That's a concurrency problem, not something a retry-safety mechanism would fix anyway.
  </Accordion>

  <Accordion title="DELETE — safe in practice" icon="trash-2">
    Deleting an already-deleted resource returns a 404, which is a perfectly good terminal state to land on after a retry — it tells you the delete already took effect. Treat a 404 on a retried `DELETE` as success, not failure.
  </Accordion>
</AccordionGroup>

## Not safe to retry blindly

**Every `POST` that creates something** — a conversation, a message, a contact, a webhook, an account, a user — has no dedupe mechanism behind it. If a `POST /api/v1/accounts/1/conversations/42/messages` times out on your end after Tied already received and processed it, retrying sends the message a second time. There's no server-side check that stops it, because there's no key to check against.

<Warning>
  This is the one that actually bites people: retrying a **message send** on a timeout doesn't fail loudly — it succeeds twice, and the customer sees the same reply sent to them twice. Before you add a naive retry-on-timeout wrapper around message creation, know that this is what it will do under real network conditions.
</Warning>

## The one built-in exception: contact creation with a stable `source_id`

The Client API's contact-creation endpoint — `POST /public/api/v1/inboxes/{inbox_identifier}/contacts` — is the one write in this API that behaves like an upsert, and only if you use it correctly.

* If you **omit** `source_id`, Tied generates a new random one for every request. Retry it, and you get a second contact\_inbox, not the same one back.
* If you **supply your own stable `source_id`** — your own user ID from your own system is the obvious choice — Tied looks for an existing contact\_inbox with that `source_id` first, and returns it unchanged if found, instead of creating a duplicate.

```bash theme={null}
curl -X POST https://app.tied.com/public/api/v1/inboxes/YOUR_INBOX_IDENTIFIER/contacts \
  -H "Content-Type: application/json" \
  -d '{"source_id": "your-own-user-id-42", "name": "Jane Doe"}'
```

Call this twice with the same `source_id` and you'll get the same contact\_inbox back both times — genuinely safe to retry. This is specific to this one endpoint; the equivalent account-scoped contact creation in the Application API (`POST /api/v1/accounts/{account_id}/contacts`) has no such lookup and will create a second contact on retry.

## What this means for how you build

* Give create operations a client-side guard — check whether the thing you're about to create already exists before you `POST`, where that's cheap to do (a contact lookup by identifier, a webhook list check by URL).
* Where a timeout leaves you genuinely unsure whether the write landed, prefer a `GET` to check before retrying the `POST` — the extra round trip is cheaper than a duplicate message.
* If you're writing a queue-backed integration (a job that retries automatically on failure), put the "did this already succeed" check in front of the write, not behind it.

## Next

<CardGroup cols={2}>
  <Card title="Errors" icon="circle-alert" href="/api-reference/errors">
    How to tell a real failure from a success you can't see yet.
  </Card>

  <Card title="Webhooks: event catalogue" icon="list" href="/api-reference/webhooks/events">
    A better way to know a write landed than guessing from a timeout.
  </Card>
</CardGroup>


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