<!-- https://cutedyno.com/docs/errors -->

# Error reference

Every error code the API returns, what causes it, and whether retrying helps.

Every failure from `/v1` and `/mcp` uses one envelope. Branch on `code`; it is stable. Never branch on `message`, which is written for humans and will change.

```json
{
  "error": {
    "code": "accounts_not_found",
    "message": "None of the supplied accountIds matched a connected account in this profile.",
    "docsUrl": "https://cutedyno.com/docs/errors#accounts_not_found",
    "requestId": "8f14e45f-ea0c-4d7a-9f1a-2b3c4d5e6f70"
  },
  "message": "None of the supplied accountIds matched a connected account in this profile."
}
```

Four fields carry the useful information:

- **`code`** — the stable identifier to switch on.
- **`message`** — a human explanation, safe to log and usually safe to show an operator.
- **`docsUrl`** — a deep link to this page, anchored at the code.
- **`requestId`** — echoes the `X-Request-Id` header. Quote it when reporting a problem and we can find the exact request.

Some codes add a `details` object. `plan_limit` carries `entitlement`, `usage`, and `limit`; `insufficient_permission` carries the `required` permission; `daily_post_limit` carries `limit`, `used`, and `resetsAt`.

## Handling errors in code

```typescript
import { CuteDynoError } from '@cutedyno/node';

try {
  await cutedyno.posts.create(input);
} catch (error) {
  if (!(error instanceof CuteDynoError)) throw error;

  switch (error.code) {
    case 'no_accounts_connected':
    case 'accounts_not_found':
      return promptUserToReconnect();

    case 'plan_limit':
    case 'trial_expired':
      return showUpgradePrompt(error.details);

    case 'rate_limit_exceeded':
      return retryAfter(error.retryAfter ?? 60);

    default:
      if (error.isRetryable) return scheduleRetry();
      log.error('CuteDyno rejected the post', {
        code: error.code,
        requestId: error.requestId,
      });
      throw error;
  }
}
```

The rule of thumb: **`4xx` is your bug or your user's state, `5xx` and `429` are ours.** Retry the second group with the same [idempotency key](/docs/guides/idempotency); fix or surface the first.

## Codes by status

### 400 — Bad request

| Code | What it means | Retry? |
| --- | --- | --- |
| `invalid_request` (Invalid request) | The request body or query string failed validation. The message names the offending field. | No |
| `invalid_profile_id` (Invalid profile id) | profileId was supplied but was not a string. | No |
| `invalid_scheduled_at` (Invalid scheduledAt) | scheduledAt could not be parsed as an ISO 8601 datetime, or it is in the past. | No |
| `invalid_state_transition` (Invalid state transition) | The post is in a state that does not allow this operation, for example editing a post that already published. | No |
| `key_not_linked_to_user` (API key not linked to a user) | Publishing records a human actor. Recreate the key from the dashboard so it carries an owner. | No |

### 401 — Unauthenticated

| Code | What it means | Retry? |
| --- | --- | --- |
| `authentication_required` (Authentication required) | No Authorization header was sent, or it was not a Bearer token. | No |
| `invalid_api_key` (Invalid API key) | The key does not exist, was revoked, or is malformed. Keys start with cdyn_live_. | No |
| `api_key_expired` (API key expired) | The key passed its expiresAt date. Create a replacement key. | No |

### 402 — Payment required

| Code | What it means | Retry? |
| --- | --- | --- |
| `trial_expired` (Trial expired) | The account trial ended. Billing affects every profile at once, so alert an operator instead of retrying. | No |

### 403 — Forbidden

| Code | What it means | Retry? |
| --- | --- | --- |
| `insufficient_permission` (Insufficient permission) | The key is missing the scope this endpoint needs, for example posts:write on a readonly key. | No |
| `profile_not_accessible` (Profile not accessible) | The requested profile is outside this key’s scope, or belongs to another account. | No |
| `plan_limit` (Plan limit reached) | A plan quota was exhausted. The response carries entitlement, usage, and limit so you can surface the reason. | No |
| `account_not_allowed` (Account not allowed) | The key is restricted to specific connected accounts and one of the supplied accountIds is not among them. | No |
| `platform_not_allowed` (Platform not allowed) | The profile policy restricts which platforms may be posted to, and one of the target accounts is on a platform outside that list. | No |
| `approval_required` (Approval required) | This profile or key forces posts through review, and no approver could be determined. Supply approvers, or recreate the key so it carries an owner. | No |
| `daily_post_limit` (Daily post limit reached) | The key has a maxPostsPerDay cap and it has been reached. The cap resets at midnight UTC. | No |

### 404 — Not found

| Code | What it means | Retry? |
| --- | --- | --- |
| `not_found` (Not found) | The resource does not exist inside the resolved profile. | No |
| `no_accounts_connected` (No accounts connected) | The profile has no connected social accounts yet. Run the connect flow first. | No |
| `accounts_not_found` (Accounts not found) | None of the supplied accountIds matched a connected account in this profile. | No |

### 409 — Conflict

| Code | What it means | Retry? |
| --- | --- | --- |
| `idempotency_key_reused` (Idempotency key reused) | This Idempotency-Key was already used with a different request body. Use a fresh key. | No |
| `profile_has_connected_accounts` (Profile still has connected accounts) | Disconnect the profile’s social accounts before deleting it, so tokens are revoked deliberately. | No |

### 415 — Error

| Code | What it means | Retry? |
| --- | --- | --- |
| `unsupported_media_type` (Unsupported media type) | The fileType passed to POST /v1/media/upload is not an accepted image or video type. | No |

### 429 — Too many requests

| Code | What it means | Retry? |
| --- | --- | --- |
| `rate_limit_exceeded` (Rate limit exceeded) | Too many requests for this key. Honour the Retry-After header before retrying. | Yes |

### 500 — Server error

| Code | What it means | Retry? |
| --- | --- | --- |
| `internal_error` (Internal error) | Something failed on our side. Retry with the same Idempotency-Key; report the requestId if it persists. | Yes |

## Legacy error format

Before this envelope existed, errors were `{ "error": "some string" }`. If you have a client written against that shape and cannot change it yet, send:

```text
CuteDyno-Error-Format: legacy
```

and responses revert to `{ "error": "<message>", "code": "<code>" }`. This is a migration aid with a limited life. Prefer reading `error.code` from the structured shape, which every current response already includes.

## MCP errors

Tool calls surface the same codes. A failing tool returns `isError: true` with the code, message, and request ID in the text, so an agent can explain what went wrong rather than retrying blindly. See [MCP](/docs/mcp).
