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

# Idempotency

> Retry a create safely on the Alvys Public API with ExternalId, how keys are compared, and why a 503 on a keyed create means retry.

A request that times out has an unknown outcome: it may have created a record, or it may not. Sending it again without a key is how a single detention charge becomes two.

The Alvys Public API gives you two separate tools, for two separate problems.

| Problem                                                                     | Tool                                 | Where                                            |
| --------------------------------------------------------------------------- | ------------------------------------ | ------------------------------------------------ |
| A retried **create** must not write twice                                   | `ExternalId` in the request body     | Accessorial and credit creates                   |
| A retried or concurrent **update** must not overwrite an edit you never saw | `If-Match` header carrying an `ETag` | Accessorial and credit updates, customers, loads |

They are not interchangeable. An idempotency key protects a record that may not exist yet; an ETag protects one that already does.

## Retrying a create with `ExternalId`

Send your own unique id with the create. If the same id arrives again, Alvys returns the record the first call created instead of writing a second one.

```json theme={null}
{
  "TripId": "trip-8842",
  "DriverId": "driver-119",
  "TypeId": "type-detention",
  "Rate": 75,
  "RateType": 1,
  "RateUom": 0,
  "Quantity": 2,
  "ExternalId": "detention-trip-8842-stop-3"
}
```

Retry the identical request and you get the original charge back. You do not get a second one, and you do not need to check first.

### The key is the request, not just the id

Reusing a key with different money is **refused**, not applied:

| You send                                                                    | Alvys answers       |
| --------------------------------------------------------------------------- | ------------------- |
| The same key, the same request                                              | The original record |
| The same key, a changed amount, quantity, driver, stop, type, trip, or load | `409 Conflict`      |
| A new key                                                                   | A new record        |

A correction is a new record, not a retry — give it a new key. An update to an existing record is a different operation entirely; see below.

<Note>
  **Scope the key to the thing it identifies.** `detention-2026-09-09` is too coarse if a trip can carry two detention charges on one day — the second is refused as a conflict. Include the stop, the event, or whatever makes this charge distinct.
</Note>

### Choosing a key

* **Stable across retries.** A key regenerated on each attempt offers no protection at all. Derive it from something in your own system — an event id, a row id — not from a timestamp or a random value created at send time.
* **Unique within your tenant.** Two tenants may use the same string safely; two charges in one tenant may not.
* **Case-sensitive**, and compared after surrounding whitespace is trimmed. `ABC` and `abc` are two different keys.
* **Optional.** Omit it and no idempotency is offered — a retry creates a second record.

### `503` means retry, not fail

A `503 Service Unavailable` on a keyed create means the write could not be confirmed and may already exist. **Retry the identical request.** Recording it as uncreated is the one response that produces a lost charge, because the record may be there.

## Updating an existing record

An update carries the opposite risk: the record exists, and someone else may have changed it since you read it. That is a precondition problem, not an idempotency one, and it is handled with an `ETag` and an `If-Match` header rather than a key.

See [Updating Records Safely](/en/api/guides/concurrency).

## Using both together

A create and an update use different mechanisms, and a robust integration uses both:

1. **Create** with an `ExternalId` you derived from your own record. A retry is safe.
2. **Keep the `ETag`** from the create response.
3. **Update** with `If-Match`. A stale validator tells you to re-read rather than silently overwriting.

Reusing the create's `ExternalId` on an update does nothing — the key belongs to the create, and an update is addressed by the record id in the path.
