---
updatedAt: 2026-09-30T17:50:34.235Z
---

Fetch the complete documentation index at: https://docs.avvio.xyz/llms.txt. Use this file to discover all available pages before exploring further. Append .md to any documentation page URL to get its markdown version.

# Publish a checkout link

`POST https://api.avvio.xyz/business/api/v1/checkout/organizations/{orgId}/links/{linkId}/publish`

Make a draft live.

`draft` to `sent`. From here `shareUrl` serves the page. For a link
offering `card` this also mints the hosted card page behind it, so the
two cannot come apart: a page that says "Card" with nothing behind it
is a buyer clicking pay and landing nowhere.

Publishing an already-live link is a no-op that returns it. A paused
or expired link cannot be published again, and a draft whose
`expiresAt` has already passed is refused with `400 LINK_EXPIRED`
before any processor call. `Idempotency-Key` is required on an API
key.

If the card processor refuses, the answer is `503` with its reason and
the link is unchanged. If it does not answer at all (a timeout or a
5xx) the answer is `500 INTERNAL`, and the card page may have been set
up: retrying straight away answers `409` ("being changed") until the
attempt clears, after which publish again.

## Parameters

- `orgId` (path, required) — The opaque organization id issued to you, normally CUID-shaped (for example `cmsx…`). It is not an `org_`-prefixed alias. Pass it unchanged in every organization-scoped path.
- `linkId` (path, required)
- `Idempotency-Key` (header, required) — A unique value per logical operation, 1-255 chars of `A-Z a-z 0-9 _ . : -`. Reuse it to retry. Same key with the same body replays the stored response; same key with a *different* body is a `409`, because answering with the first call's result would hand you a receipt for a payout you did not request. A `4xx` releases the key, so you can fix the body and reuse it. **Reuse it; do not generate one per attempt.** A key minted per attempt defeats replay entirely: every retry looks like a new request, so every retry pays. We also watch for an identical body arriving under a *different* key within 15 minutes and refuse it with `DUPLICATE_REQUEST_DETECTED`. Records are kept for 7 days. That is a retention window only: there is no path where an expired key is re-executed.

## Example

```bash
IDEMPOTENCY_KEY=$(uuidgen)  # new key per call; reuse it only to retry this exact request
curl -s -X POST "$AVVIO_BASE_URL/checkout/organizations/$AVVIO_ORG_ID/links/$linkId/publish" \
  -H "x-api-key: $AVVIO_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY"
```

## Responses

- `200` — The live link, `status: sent`, with `shareUrl`.
- `400` — `VALIDATION_ERROR` (a field is malformed; `errors` names each), `BAD_REQUEST` (a request we understood but cannot carry out, named in `detail`: editing a published link, deleting one that was live, both `productId` and `items`), `LINK_EXPIRED` (publishing a draft whose `expiresAt` has passed), or, on a write, `IDEMPOTENCY_KEY_REQUIRED` / `IDEMPOTENCY_KEY_INVALID` (the header is missing or malformed).
- `401` — The key was refused. Nothing ran. - `UNAUTHORIZED`: missing, invalid or revoked, or a key on a route that does not accept one. - `KEY_EXPIRED`: the key passed the expiry it was issued with. Issue a new one; an expired key cannot be rotated. - `KEY_IP_NOT_ALLOWED`: the key is pinned to source addresses and this request came from another.
- `403` — A valid key that may not make this write. Nothing was changed. - `FORBIDDEN`: the key belongs to a different organization, **or** your business has not completed verification to accept payments; `detail` says which. - `ACCOUNT_BLOCKED`: API access for your organization is suspended. - `LIVE_KEY_ORG_NOT_APPROVED`: a live key, before we have approved your business verification. Use a test key until then. - `INSUFFICIENT_SCOPE`: a read-only key.
- `404` — No such link or product in this organization, or the id belongs to another one.
- `409` — `CONFLICT`: another publish or edit of this link is in flight (retry after a moment), or the draft was repriced after its card page was set up ("Delete this draft and create a new one"). Or an idempotency conflict (`IDEMPOTENCY_KEY_CONFLICT`, `IDEMPOTENCY_KEY_REQUEST_IN_PROGRESS`).
- `429` — Too many requests. The default ceiling is **100 requests per minute per API credential** on a 60-second window. High-volume payout and reconciliation routes declare a 600/minute override, and batch submission a 30/minute ceiling. A separate 2,000/minute per-source-IP abuse ceiling always applies. Obey `Retry-After`; it is in seconds and is authoritative. A 429 means the request was refused before the handler ran. Retry reads normally; retry an idempotent mutation with its same `Idempotency-Key`.
- `500` — `INTERNAL`: the card processor did not answer while the card page was being set up. The link is still a draft, but the page may exist; a publish retried at once answers `409` until the attempt clears.
- `503` — `INTERNAL` (status 503): card payments are not set up on this environment, or the card processor refused to set up this link (its reason is in `detail`). The link is unchanged and stays a publishable draft.

Machine contract: [partner-checkout.openapi.yaml](/partner-checkout.openapi.yaml), operation `publishCheckoutLink`.
