# Idempotent requests

> Send an Idempotency-Key header so that retrying a payment or an order does not charge you or order twice.

Source: https://www.coritan.com/docs/api/idempotency/

Some requests must happen only once: a payment, an order, a new database. When the connection drops before the answer arrives, you cannot tell whether the request went through. Send a key with the request, and a retry with the same key answers what the first request did instead of doing it again.

## Send a key

- Make a new random key for each operation you mean to happen once. A UUID fits every route on this page, and `uuidgen` prints one.
- Send the same key, with the same body, on every retry of that operation.
- Wait for an answer, or for your client to time out, before you retry.
- Make a new key for a new operation, including a second try after a card is declined.

```bash
KEY=$(uuidgen)
curl -X POST https://api.coritan.com/api/v1/payments/invoices/5011/charge \
  -H "Authorization: Bearer $CORITAN_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $KEY" \
  -d '{"payment_method_id": 812}'
```

To retry, run the same command with the same `$KEY`.

## Routes that take a key

| Route | Where the key goes | Format | How long it counts |
| --- | --- | --- | --- |
| [`POST /payments/invoices/{invoice_id}/charge`](/docs/api/reference/client/payments/#op-post-api-v1-payments-invoices-invoice-id-charge), and the older `/pay` | `Idempotency-Key` header | Up to 255 characters | For as long as the payment attempt exists, with no expiry |
| [`POST /services/order`](/docs/api/reference/client/services/#op-post-api-v1-services-order) for a floating IP | `idempotency_key` in the body, required | 1–64 letters, digits, dots, underscores, colons or hyphens | Until the order fails, is cancelled or expires |
| [`POST /client/servers/{uuid}/databases`](/docs/api/reference/client/container-apps/servers-databases/#op-post-api-v1-client-servers-uuid-databases) | `Idempotency-Key` header, or `idempotency_key` in the body | Letters, digits, hyphens and underscores. Other characters are dropped, and only the first 64 count. | Until the database is deleted |
| [`POST /chat/conversations`](/docs/api/reference/client/support/#op-post-api-v1-chat-conversations) and [`POST /chat/conversations/{conversation_id}/messages`](/docs/api/reference/client/support/#op-post-api-v1-chat-conversations-conversation-id-messages) | `client_request_id` in the body | Up to 64 characters | For as long as the conversation or the message exists |
| [`POST /orgs/{org_slug}/storefront/order`](/docs/api/reference/organizations/storefront/storefront/#op-post-api-v1-orgs-org-slug-storefront-order) | `Idempotency-Key` header | Only the first 128 characters count | No expiry |
| [`POST /orgs/{org_slug}/portal/services/{service_id}/change-plan`](/docs/api/reference/organizations/customer-portal/services/#op-post-api-v1-orgs-org-slug-portal-services-service-id-change-plan) | `Idempotency-Key` header | Only the first 128 characters count | While its answer still describes the service |
| [`POST /orgs/{org_slug}/portal/ips/order`](/docs/api/reference/organizations/customer-portal/ips/#op-post-api-v1-orgs-org-slug-portal-ips-order) | `idempotency_key` in the body, required | 1–64 letters, digits, dots, underscores, colons or hyphens | Until the order fails, is cancelled or expires |
| [`POST /orgs/{org_slug}/portal/servers/{uuid}/databases`](/docs/api/reference/organizations/customer-portal/servers-databases/#op-post-api-v1-orgs-org-slug-portal-servers-uuid-databases) and [`POST /orgs/{org_slug}/staff/servers/{uuid}/databases`](/docs/api/reference/organizations/org-staff-containers/staff/#op-post-api-v1-orgs-org-slug-staff-servers-uuid-databases) | `Idempotency-Key` header, or `idempotency_key` in the body | Up to 64 characters | Until the database is deleted |
| [`POST /orgs/{org_slug}/store/carts/{cart_id}/complete`](/docs/api/reference/organizations/commerce-store-api/#op-post-api-v1-orgs-org-slug-store-carts-cart-id-complete) | `Idempotency-Key` header | Up to 128 characters. A longer key answers `422`. | 24 hours |

## What a repeat answers

Payments
: The first payment attempt as it is now, with the same `id`. Nothing is charged a second time. Once that payment has paid the whole invoice, a repeat answers `400` with `No remaining balance on invoice` instead, so read the invoice to confirm it is paid. A declined attempt stays declined under its key: send a new key to try again.

Floating IP orders
: The first order. `POST /services/order` sets `message` to `Order already submitted` within two minutes of the first request, and to `Replayed existing IP order` once the order has an invoice. `POST /orgs/{org_slug}/portal/ips/order` adds `"replay": true`. [Order a floating IP](/docs/floating-ips/order-a-floating-ip/) describes the order and its statuses.

Databases
: The database the first request created on that server, with its password. When the first request stopped part way, the repeat finishes creating it.

Support conversations and messages
: The conversation or the message the first request created.

Storefront orders
: The answer the first request received.

Plan changes
: The answer the first request received, while it still describes the service: an upgrade whose invoice is still waiting to be paid, or a change to the plan the service is still on. Otherwise the request runs again, so a key you send after moving to another plan asks for the change again.

Store API cart completion
: The status and the body the first request received. Completing a cart that already has its order answers that order again, with or without a key.

When the first request answered an error, a retry with the same key runs the request again.

## Errors

| Status | Answer | What it means |
| --- | --- | --- |
| `409` | `An identical IP order is already in progress; retry in a moment` | The first floating IP order with this key is still running. Wait a moment and send it again. |
| `409` | `Order already in progress` | The first storefront order with this key is still running. |
| `409` | `Plan change already in progress` | The first plan change with this key is still running. |
| `409` | `"error": "idempotency_in_progress"` | The first cart completion with this key is still running. |
| `409` | `"error": "idempotency_key_reused"` and `This Idempotency-Key was used for a different request.` | The key was used to complete another cart. Make a new key. |
| `409` | `"error": "database_busy"` and `That database is being deleted` | The database this key created is being deleted. Make a new key. |
| `422` | `idempotency_key is required for IP orders` | A floating IP order was sent without a key. |
| `422` | A message that starts `idempotency_key must be` | The key has a character or a length a floating IP order does not accept. |

Make a new key for each different storefront order or plan change. A key sent again with a different product, plan, hostname or `config` fails.

## Repeated orders without a key

`POST /services/order` also catches a repeat that carries no key. An order for the same product, price and `hostname` as one you placed in the last two minutes answers that first order, with `message` set to `Order already submitted`, while the first is still `pending`. This holds for every product, and for a floating IP order whatever key it sends. Other products accept `idempotency_key` and ignore it. To order two services on the same plan at once, give each its own `hostname`. [Order a service](/docs/get-started/order-a-service/) describes the order and its answer.

`POST /orgs/{org_slug}/storefront/order` does the same for an organization's customers, with or without a key, and marks the answer `"replayed": true`.

## Routes without a key

Other routes take no key, and a repeat is a new request. Before you retry one whose answer you did not receive, read the record it changes: list your invoices to see whether a payment went through, or your services to see whether an order arrived.

The customer portal's [`POST /orgs/{org_slug}/portal/invoices/{invoice_id}/charge`](/docs/api/reference/organizations/customer-portal/invoices/#op-post-api-v1-orgs-org-slug-portal-invoices-invoice-id-charge) takes no key. Once the invoice is paid, it answers `404` with `Invoice not found or already paid`.
