# Responses and errors

> The summary and data envelope every Xholic surface returns, the stable error codes, control fields and headers.

Source: https://docs.xholic.ai/api/responses/

Every action answers the same way on REST, MCP and the CLI.

## Success

```json
{
  "ok": true,
  "summary": "Scheduled for Tue 28 Oct, 10:00 (America/New_York) on @yourhandle.",
  "data": {
    "postId": "…",
    "scheduledAt": "2026-10-28T14:00:00.000Z"
  },
  "ids": {
    "postId": "…"
  }
}
```

| Field | Meaning |
| --- | --- |
| `summary` | One or two readable sentences. Read it first; models can relay it as is. |
| `data` | The result. Its shape depends on the action. |
| `ids` | IDs worth acting on next, such as `postId`. |
| `next` | Suggested next calls, by MCP tool name. |
| `creditsCharged` | AI credits actually spent, when any. |
| `dryRun` | `true` when nothing was changed because you passed `dryRun`. |

## Errors

```json
{
  "ok": false,
  "error": {
    "code": "SCOPE_MISSING",
    "message": "This connection isn't allowed to post, send and change what goes out on X yet. Give the user this link to allow it (one click in Xholic), then try again: https://app.xholic.ai/oauth/allow?connection=…&scope=publish",
    "scope": "account",
    "details": {
      "missingScope": "publish",
      "allowUrl": "https://app.xholic.ai/oauth/allow?connection=…&scope=publish"
    }
  }
}
```

`code` is stable; branch on it, not on the message. `scope` says whose problem it is: `account` means something you or the account can fix, `platform` means Xholic or X is having trouble and a retry later should work. `retryAfterSeconds` is set when waiting helps, and `details` carries more (validation issues, a credit quote).

On MCP the same object comes back as a tool result with `isError: true`, and the text starts with `CODE: message`.

| Code | HTTP | Scope | Meaning |
| --- | --- | --- | --- |
| `UNAUTHENTICATED` | 401 | account | No credential was sent, or the action needs an account. |
| `INVALID_CREDENTIAL` | 401 | account | The API key or token is wrong, revoked or expired. |
| `SCOPE_MISSING` | 403 | account | The connection lacks the write or publish scope this action needs. `details.allowUrl` lets the person allow it in one click. |
| `ROLE_NOT_PERMITTED` | 403 | account | Your role on a shared account doesn't allow this action. |
| `ACCOUNT_NOT_FOUND` | 404 | account | The `account` isn't one this login can use. |
| `SUBSCRIPTION_REQUIRED` | 402 | account | The subscription isn't active. Keys and connections are kept. |
| `FEATURE_NOT_INCLUDED` | 403 | account | The plan doesn't include this feature. |
| `APP_ONLY` | 403 | account | This change can only be made in the Xholic app. |
| `INVALID_INPUT` | 400 | account | The input failed validation; `details` lists each field. |
| `NOT_FOUND` | 404 | account | The thing you referenced doesn't exist, or the endpoint doesn't. |
| `CONFLICT` | 409 | account | The change clashes with the current state (for example an old article version). |
| `CREDITS_CONFIRMATION_REQUIRED` | 402 | account | The action uses Xholic AI. `details.quote` has the estimate and a `quoteId`; call again with `confirmCredits` and `quoteId`. |
| `INSUFFICIENT_CREDITS` | 402 | account | Not enough AI credits left for this action. |
| `RATE_LIMITED` | 429 | account | Too many requests in this bucket. Wait `retryAfterSeconds`. |
| `IDEMPOTENCY_KEY_REUSED` | 409 | account | The key was already used for a different request. |
| `IDEMPOTENCY_IN_PROGRESS` | 409 | account | A request with this key is still running. Retry with the same key shortly. |
| `X_DATA_LIMIT_REACHED` | 429 | account | The plan's live X data allowance is used up for now. |
| `UPSTREAM_UNAVAILABLE` | 503 | platform | X or another service Xholic depends on is unavailable. Retry later. |
| `INTERNAL_ERROR` | 500 | platform | Something failed on Xholic's side. Retry later. |

## Control fields

Every surface adds these to an action's own input:

| Field | Type | Applies to | What it does |
| --- | --- | --- | --- |
| `account` | string | Every action that acts on an X account | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | Every change | Return exactly what would happen without doing it. |
| `idempotencyKey` | string | Every change (required on `publish_now`) | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |
| `confirmCredits` | number | Actions that use Xholic AI | Xholic AI credits the user agreed to spend. Call first without it to get the quote, show the quote to the user, and pass the amount only after they say yes. |
| `quoteId` | string | Actions that use Xholic AI | The quoteId from the quote the user agreed to. Send it with confirmCredits. |

## Headers (REST)

| Header | Meaning |
| --- | --- |
| `X-RateLimit-Limit` | Requests allowed per minute in this action's bucket. |
| `X-RateLimit-Remaining` | Requests left this minute. |
| `X-RateLimit-Reset` | Seconds until the bucket refills. |
| `X-Xholic-Credits-Remaining` | AI credits left, on actions that quote credits. |
| `Retry-After` | Seconds to wait, on `429` and some `409`/`503` errors. |

Responses are `Cache-Control: no-store`. The API takes bearer credentials only and never reads cookies.
