---
url: /errors.md
---

# Errors & idempotency

## The error shape

Every error is the same JSON: a machine-readable `error` code, plus any context
fields that help.

```json
{ "error": "connection_inactive", "platform": "youtube", "status": "reauth_required" }
```

Branch on `error`, not on the prose. Read context fields when present.

## Status codes

| Status | Meaning |
| --- | --- |
| `400` | the request body or parameters failed validation |
| `401` | missing or invalid credentials |
| `403` | authenticated, but the account has no workspace yet (`no_workspace`) |
| `404` | the resource doesn't exist, or isn't visible to this caller — including a `{brandId}` that isn't one of your brands (`brand_not_found`) |
| `409` | a conflict: an idempotency key was reused, or the resource is in a non-retryable state |
| `422` | a referenced resource (asset, connection, cover) is invalid for the operation |
| `501` | a capability isn't configured on this deployment |
| `500` | an unexpected fault on our side |

A `422` is the one to read closely: it carries *which* reference was wrong.
Publishing surfaces `asset_not_ready`, `connection_not_found`,
`platform_mismatch`, `connection_inactive`, `platform_not_connected`, and
`duplicate_platform`, each with the offending resource or platform identifier, so
you can fix the target rather than guess.

## Idempotency

The write that matters, **`POST /brands/{brandId}/publications`** (and
`POST /brands/{brandId}/publications/{id}/targets`), accepts an `Idempotency-Key` header. Send a
unique key per logical publish, and a retried request with the same key replays
the first response instead of publishing twice.

First, list your brands and select the brand ID:

```sh
# List your brands and pick one (the id looks like brand_…)
curl -s "$API/brands" -H "Authorization: Bearer $SK"
export BRAND="brand_…"
```

Then send the request targeting the brand-scoped publications endpoint:

```sh
curl -X POST "$API/brands/$BRAND/publications" \
  -H "Authorization: Bearer $SK" -H "Idempotency-Key: 7c3f…" \
  -H "Content-Type: application/json" -d '{ … }'
```

* **Same key, same body** → you get the original response back, no second publish.
* **Same key, different body** → `409 idempotency_key_reused`. A key is bound to the request it first saw.
* **Same key, still in flight** → `409 request_in_progress`. Wait and retry.

Validation runs *before* the key is claimed, so a request rejected for a bad body
doesn't burn the key. Fix it and retry with the same one.

Keys aren't required. A keyless write simply runs every time, so generate a key
(a UUID is ideal) whenever a retry must be safe.

**Generate the key — don't derive it from your content.** Mint a fresh UUID when
you start a publish, keep it, and resend that *same* value only if you have to
retry the *same* call. A key derived from the request body or the video (a hash
of either) looks idempotent but isn't: a re-upload produces a different asset, so
the body changes and the derived key collides across two genuinely distinct
publishes (`409 idempotency_key_reused`) — while a real retry needs the value to
stay identical, which a fresh hash won't give you either.
