---
title: "Retries"
description: "Which calls are safe to repeat, when a repeat may help, and what to do when a write's outcome is unknown."
---

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

# Retries

Arbol's SDKs send each call once and never retry it, so your code decides. Two questions decide it: may a repeat succeed, which the error's `retryable` answers, and is a repeat safe, which the operation's effect answers.

## Each operation's effect

Every operation's page in the API reference states its **Effect**:

| Effect | What the operation does | Repeating it |
| --- | --- | --- |
| Reads | Changes nothing. | Safe. |
| Writes | Creates or changes something. | Safe only if the operation is idempotent, or once you've confirmed the first attempt didn't apply. |
| Deletes | Deletes or cancels something. | Same as a write. |

The HTTP method doesn't tell you the effect. [`agenda.bookings.lookup`](/api/agenda/bookings/lookup/) is a `POST` that reads, and [`agenda.bookings.cancel`](/api/agenda/bookings/cancel/) is a `POST` that deletes: read the operation's Effect instead.

## When a repeat may help

`error.retryable` says whether repeating the same request may succeed. These common codes are retryable:

| Status | `code` | What to do |
| --- | --- | --- |
| `429` | `RATE_LIMITED` | Too many requests from this credential. Wait the seconds in `Retry-After`, then repeat. |
| `503` | `UPSTREAM_UNAVAILABLE` | Back off, then repeat. |
| `504` | `UPSTREAM_TIMEOUT` | Back off, then repeat a read. A write may have applied: see below. |

Services add retryable codes of their own, such as `PROVIDER_UNAVAILABLE` in gMedic, SURA and Visoft. When `retryable` is `false`, the same request will fail again: change the input, the credential or the state first.

## Writes with an unknown outcome

Some failures don't say whether a write happened:

- **`502 OUTCOME_UNKNOWN`**: the operation may have been applied. Check its result before repeating it.
- **A timeout, or no answer at all.** A write that ended with `504 UPSTREAM_TIMEOUT`, or whose connection dropped, may have applied too.

Reconcile before you repeat: read the record, or list what the first attempt would have created. Several operations say so in their description, such as [`gmedic.patients.create`](/api/gmedic/patients/create/) ("reconcile an unknown outcome before repeating"), [`visoft.appointments.create`](/api/visoft/appointments/create/) ("Never repeat a booking whose outcome is unknown") and [`raiz.channels.create`](/api/raiz/channels/create/), which isn't idempotent: list the channels before trying again.

The Rust SDK labels every failure with an `outcome`: `Refused` (nothing happened), `NotSent` (the request never left) or `Unknown` (a write that may have happened).

## Operations that are safe to repeat

Some writes are designed to be repeated:

- **Idempotent operations.** An operation's Effect says *idempotent* when repeating it with the same input has no further effect, and *not idempotent* when a repeat may act again. Among writes, [`raiz.agents.update`](/api/raiz/agents/update/) and [`agenda.waitlist.join`](/api/agenda/waitlist/join/) are idempotent, while [`raiz.contacts.create`](/api/raiz/contacts/create/) and [`sura.appointments.create`](/api/sura/appointments/create/) aren't. MCP clients see the same as the tool's `idempotentHint`.
- **Idempotency keys.** [`agenda.appointments.create`](/api/agenda/appointments/create/) takes an `idempotencyKey`, unique within the organization: reusing it returns the original appointment. [`sura.appointments.create`](/api/sura/appointments/create/) takes one too, reused only for the same accepted offer.
- **Repeats that change nothing.** Calling [`agenda.waitlist.join`](/api/agenda/waitlist/join/) again for the same patient, service and reason returns the same open entry. Cancelling a Raíz task again with [`raiz.tasks.cancel`](/api/raiz/tasks/cancel/) changes nothing.
- **Version checks.** Updates that take `expectedVersion` apply only if the record hasn't changed since you read that version. In Raíz, repeating an update that already applied fails with `VERSION_CONFLICT` instead of applying twice.

## Pending answers in SURA

A SURA call may answer `"status": "pending"` with an `operationId` and `retryAfterSeconds` instead of its result, while SURA finishes an earlier request. The operation keeps running. After `retryAfterSeconds`, get the result with [`sura.operations.get`](/api/sura/operations/get/), or repeat the identical call, which returns the same operation instead of starting another.

## A retry policy

1. **Respect `retryable: false`.** Fix the request before sending it again. The exception is `OUTCOME_UNKNOWN`: check whether the write applied, and repeat it only if it didn't.
2. **Repeat reads** when `retryable` is `true`, or when no answer came back.
3. **Repeat writes and deletes** only when the operation is idempotent, or once you've confirmed the first attempt didn't apply.
4. **Wait between attempts.** Use `Retry-After` when the answer has it. Otherwise, back off exponentially with jitter, and cap the number of attempts.
5. **Log each attempt's `X-Request-Id`**, so a failure can be traced.

Source: https://docs.getarbol.com/en/developers/concepts/retries/index.mdx
