Authentication
Every request carries a bearer credential:
Authorization: Bearer <API key or OAuth access token>The API and MCP server never read cookies, so there is no browser session to hijack and no CORS setup to get wrong. A credential that is wrong, revoked or expired is rejected with INVALID_CREDENTIAL; it never falls back to anonymous access. A Xholic sign-in session is not an API credential either: it is refused, so a session copied out of a browser can’t drive the API.
Least access by default
Section titled “Least access by default”Every connection starts with as little as it needs, and gets more only when you say so:
- Read and Write are on; Publish (posting, sending, changing what goes out) starts off. The first time your AI needs it, the call answers
SCOPE_MISSINGwith a link; you allow it in one click and the AI tries again. - One X account (your own) unless you tick more. A connection can’t see or act on accounts you leave unticked.
- Unused connections switch off. A key or app that hasn’t made a call in 90 days stops working; create a new key or connect the app again.
API keys
Section titled “API keys”For scripts, servers, CI and MCP clients without OAuth.
- Create them in Settings → API / MCP / CLI. A key looks like
xhk_…and is shown once; Xholic stores only a hash and shows its last four characters afterwards. - Give each key a name, the scopes and X accounts it needs and, if you like, an expiry date. Up to 10 keys per user.
- Revoking a key takes effect within seconds. The Last used time shows which keys are still in use.
Keep keys out of code and client-side apps. Anyone with a key can act as you within its scopes.
For MCP clients such as Claude, ChatGPT, Claude Code and Cursor, and for the CLI’s xholic login. You never handle a token yourself.
- The client calls
https://api.xholic.ai/mcpwithout a token and gets401with aWWW-Authenticateheader pointing to/.well-known/oauth-protected-resource. - That document names Xholic’s authorization server. The client reads its metadata (
/.well-known/oauth-authorization-server), registers itself (dynamic client registration) and starts an OAuth 2.1 authorization code flow with PKCE. - You sign in to Xholic and see the consent screen: the app’s name, the scopes (Read and Write ticked, Publish off), the X accounts it can use and its default account.
- The client receives an access token and a refresh token and uses them from then on. Each refresh token works once: refreshing returns a new pair and the old one stops working.
The consent screen is shown on every new authorization, even when a client asks to skip it. Connected apps are listed next to your API keys, and revoking one ends its access.
Read, Write and Publish are chosen on the consent screen, not as OAuth scopes; the authorization server issues openid, profile and offline_access.
Login code
Section titled “Login code”xholic login prints a short code such as K7QM-2XPD, and the consent screen shows the same code. Approve only if they match: a different code (or one you didn’t start) means the request in your browser isn’t the one your terminal is waiting for.
Scopes
Section titled “Scopes”| Scope | Allows | Examples |
|---|---|---|
read |
Reading data. Never changes anything. | get_me, get_queue, get_analytics, search_x |
write |
Changing data in Xholic. | save_draft, remember, create_worker, add_to_contact_list |
publish |
Acting on X: posts, replies, DMs, follows. | schedule_post, publish_now, send_dms |
Each action needs exactly the scope of its kind; the reference lists it. A call without the scope fails with SCOPE_MISSING, and error.details.allowUrl is the one-click page to allow it. MCP clients still see those tools, so the AI can hand you that link. You can change a connection’s scopes and accounts later in Settings.
When an MCP app can show a confirmation (MCP elicitation), Publish actions also wait for your yes in the app before they run; see Confirm before posting.
Accounts
Section titled “Accounts”A connection covers the X accounts you chose for it, out of the ones your Xholic login can use: your own, accounts you added to your plan, and accounts shared with you.
list_accounts(accounts.list) returns them, each marked as your own, added or shared.- Actions act on the connection’s default account unless you pass
accountwith an@handle:
{ "account": "@brandaccount", "text": "…", "scheduledAt": "2026-11-03T09:00", "timezone": "UTC" }- On shared accounts your role applies exactly as in the app. An Editor can write and schedule but not send DMs or change settings; such calls fail with
ROLE_NOT_PERMITTED. The account’s owner sees what your connections did on it in their Settings. - Account-wide actions (settings, connections) take no
account.
Plans and lapsed subscriptions
Section titled “Plans and lapsed subscriptions”API, MCP and CLI access is part of every plan, including the trial. Plan gates are the app’s own: Workers, Signals, DMs and Articles need the plan that includes them (FEATURE_NOT_INCLUDED otherwise).
If a subscription lapses, keys and connected apps are kept, and calls return SUBSCRIPTION_REQUIRED until it is active again. Connections stay visible and revocable in Settings throughout.
Free tools without an account
Section titled “Free tools without an account”https://api.xholic.ai/mcp/free needs no credential at all. See Free tools.
