# Candor API — full reference Connect an agent (Claude Code): claude mcp add --transport http candor https://candorai.dev/mcp --header "Authorization: Bearer $CANDOR_API_KEY" Remote MCP: https://candorai.dev/mcp Base URL: https://candorai.dev/v1 Format: OpenAI Chat Completions compatible. Any OpenAI SDK works with base_url="https://candorai.dev/v1". Getting started: 1. Sign in at https://candorai.dev. New accounts get $0.47 of free credit to try the API, once per person. 2. When the free credit runs out, subscribe at https://candorai.dev/?panel=account: Monthly $19.99/month, includes 8,500 credits ($20.00 of API usage); or Plus $99/month, includes 42,500 credits ($100.00 of API usage). 3. Optional top-ups at the same page: $9.99 for 4,250 credits ($10.00 of API usage), subscribers only. 4. Create a key under Account > API keys (shown once). A key can be given a spending limit in USD (in total, per day or per month) and an end date, for example one key per project or per agent: it stops at its limit, or on its date, without touching the rest of the account. Both can be changed later on the same screen. If the balance runs out, requests return HTTP 402 insufficient_quota with this guidance: "Subscribe at https://candorai.dev/?panel=account: Monthly $19.99/month, includes 8,500 credits ($20.00 of API usage), or Plus $99/month, includes 42,500 credits ($100.00 of API usage). Subscribers can also add top-ups there: $9.99 for 4,250 credits ($10.00 of API usage). Then retry." Auth: header "Authorization: Bearer sk-unc-". Keys are shown once; only a SHA-256 hash and a short hint (the first and last characters) are stored. ## Billing - New accounts get $0.47 of free credit to try the API, once per person. While an account has only free credit, it is allowed 10 requests per minute and max_tokens is capped at 2,000 per request (larger values are clamped). Free credit is spent first, then paid credit. - Requests spend the same credit wallet as the app: monthly subscription credits (Monthly $19.99/month, includes 8,500 credits ($20.00 of API usage); Plus $99/month, includes 42,500 credits ($100.00 of API usage)) and top-ups ($9.99 for 4,250 credits ($10.00 of API usage), subscribers only), bought at https://candorai.dev/?panel=account. - Exact conversion: 1 USD = 425 app credits. - Every response says what it cost: usage.cost_usd is the exact charge for that request (in the last chunk of a stream when stream_options.include_usage is true). - Each request is charged from the token usage the model reports: (prompt - cached) x input + cached x cached input + completion x output. Reasoning tokens are part of completion tokens and are not charged twice. - You can never spend more than your balance. Before a request runs, the most it could cost is set aside (held): the prompt, counted at one token per byte (8,192 per image), plus max_tokens of output, never less than $0.01. When the request ends the hold is replaced by the actual charge, which is usually several times smaller. Parallel requests each need their own hold. - Set max_tokens to hold less. Without it the hold covers the model's full output limit. If the balance cannot cover the output you asked for, max_tokens is lowered to what it can pay for and the response header x-candor-max-tokens gives the value used. - A request is refused with HTTP 402 (type insufficient_quota) when the balance is under $0.01 (code insufficient_quota), or when it cannot cover the prompt plus 256 output tokens (code request_exceeds_balance: send a shorter prompt or add credits). Nothing is charged. ## Models and prices (USD per 1M tokens) | Model | Input / 1M | Cached input / 1M | Output / 1M | Context | Max output | Vision | Reasoning | | --- | --- | --- | --- | --- | --- | --- | --- | | `candor-fast` | $0.45 | $0.045 | $1.45 | 200,000 | 24,000 | yes | yes | | `candor-classic` | $1.00 | $0.10 | $3.00 | 128,000 | 8,192 | yes | no | | `candor-smart` | $3.00 | $0.30 | $5.00 | 1,000,000 | 131,072 | no | yes | ## POST /v1/chat/completions Request body (JSON): - model (string, required): one of candor-fast, candor-classic, candor-smart. The former ids uncensored-fast, uncensored and uncensored-smart still work as aliases of candor-fast, candor-classic and candor-smart; responses always report the new id. - messages (array, required): {role: system|developer|user|assistant|tool, content: string | content parts | null, name?, tool_calls?, tool_call_id?}. Image parts ({"type":"image_url","image_url":{"url":"data:..."}}) work on vision models. - stream (boolean): server-sent events, each line "data: {chat.completion.chunk}", ending with "data: [DONE]" - stream_options.include_usage (boolean): add a final chunk with empty choices and usage - max_tokens / max_completion_tokens (integer): capped at the model's max output; it is also what the request holds for output (see Billing) - temperature (0-2), top_p (0-1), frequency_penalty, presence_penalty (-2..2), stop (string or up to 4 strings), seed (integer) - tools, tool_choice, parallel_tool_calls: OpenAI function-calling format; tool_calls are returned in message.tool_calls / delta.tool_calls - response_format: {"type":"json_object"} or {"type":"json_schema", ...} - reasoning_effort: none | low | medium | high | max (reasoning models only; ignored otherwise). Reasoning text is returned in message.reasoning_content when the model provides it. - character (string): the id of a character from GET /v1/characters; the model answers as that character (see Characters) - web_search (true, or {"query": string}): search the web first and give the model the results (see Web search) - n: only 1 is supported. Other unknown fields are ignored. Response: {id, object:"chat.completion", created, model, choices:[{index, message:{role, content, tool_calls?, reasoning_content?}, finish_reason}], usage:{prompt_tokens, completion_tokens, total_tokens, prompt_tokens_details:{cached_tokens}, completion_tokens_details:{reasoning_tokens}}} Example: curl https://candorai.dev/v1/chat/completions \ -H "Authorization: Bearer $CANDOR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"candor-classic","max_tokens":512,"messages":[{"role":"user","content":"Hello"}]}' ## GET /v1/models No key needed. Returns {object:"list", data:[{id, object:"model", owned_by, context_length, max_output_tokens, capabilities, pricing:{input_per_million, cached_input_per_million, output_per_million}}]}. GET /v1/models/{id} returns one model. ## GET /v1/balance Returns {object:"balance", currency:"usd", available_usd (paid), trial_usd (free), spendable_usd, reserved_usd, available_credits, trial_credits, trial_spendable_via_api, free_credit_limits, minimum_request_usd, can_request, api_key:{id, spend_limit_usd, spend_limit_period, spent_usd, remaining_usd, resets_at, expires_at}}. api_key describes the calling key: spend_limit_usd, remaining_usd and expires_at are null for a key without a limit or an end date. spend_limit_period is "day" or "month" for a limit that starts again (days and months are UTC), then spent_usd counts from the start of that day or month and resets_at says when it starts again; it is null for a limit that counts the key's whole life. can_request is false when either the account balance or the key's remaining limit is under the minimum. ## Web search Same key, same wallet and same per-minute limit as chat. A search costs about $0.029 (the exact charge is in the response); $0.080 is set aside while it runs. A search that could not run costs nothing. Only the search words go to the search provider, never the conversation. ### POST /v1/web/search Body: {"query" (required, 2 to 300 characters)}. Returns up to 6 results: {"id":"search-...", "object":"web_search", "created", "query", "results":[{"title", "url", "snippet"}], "usage":{"cost_usd", "credits"}} An empty "results" means the search ran and found nothing (it is charged). HTTP 502, code search_unavailable: the search could not run, nothing was charged, retry shortly. Use it as the function behind a tool your own model calls, or on its own. curl https://candorai.dev/v1/web/search \ -H "Authorization: Bearer $CANDOR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query":"price of a monthly Navigo pass in 2026"}' ### "web_search" in POST /v1/chat/completions Add "web_search": true and Candor searches the web for the last user message before the model answers; or "web_search": {"query": "..."} to choose the words. The results are given to the model as a system message, numbered, and the model is told to name its sources like [1]. - The response carries "web_search": {"id", "query", "results":[{"title","url","snippet"}], "cost_usd"} next to "choices". In a stream it is in the first chunk. - usage.cost_usd is the whole request (tokens and search); usage.web_search_cost_usd is the search alone. The results count as input tokens. - One search per request. The search is refused (HTTP 402) unless the balance covers both it and the model call. A search that ran stays charged even if the model call then fails. curl https://candorai.dev/v1/chat/completions \ -H "Authorization: Bearer $CANDOR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"candor-smart","web_search":true,"max_tokens":400,"messages":[{"role":"user","content":"What changed in the latest Python release?"}]}' ## Characters Candor's characters (companions, roleplay, stories, games, advisors) can be played through the API. Add "character" to a chat request and the model answers as that character: the server adds the character's instructions as the first system message. Everything else is an ordinary chat completion, on the model you name, at that model's price. ### GET /v1/characters and GET /v1/characters/{id} Query (all optional): category (girlfriends, boyfriends, roleplay, games, advisors), format (text: short messages like a phone chat; story: narrated scenes), search (words in the name, tagline or tags), owner (candor: the catalogue; you: characters you wrote in the app). Returns {object:"list", data:[{id, object:"character", owned_by: candor|you, name, age?, category?, format, tagline, tags, greeting, opener?, image_url?, avatar_url?, added?, prompt_tokens_estimate}]}. - greeting is the character's first message: show it to your user before their first reply, and send it back as an assistant message so the character remembers saying it. - prompt_tokens_estimate is about how many input tokens the character's instructions add to every request. They are billed like any input, and the amount set aside before the call counts them at one token per byte. - A character you wrote in the app has an id starting with "own:" and only works with your own keys. ### Talking to a character POST /v1/chat/completions with "character": "". Send the whole conversation each time, as with any chat completion: the API keeps nothing between requests, so the character knows only what is in "messages". Your own system messages are kept and read as context from you (the user's name, the scene, the language), after the character's instructions. Unknown id: HTTP 404, code character_not_found. candor-classic suits characters best. Photos and voice notes are not sent through the API. curl https://candorai.dev/v1/chat/completions \ -H "Authorization: Bearer $CANDOR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"candor-classic","character":"brooke","max_tokens":300,"messages":[{"role":"user","content":"hey, how was your day?"}]}' ## GET /v1/usage Query: limit (1-100, default 50), before (cursor from next_before), key_id (a key id, or "current" for the calling key). Returns {object:"list", data:[{id, created, model, api_key_id, stream, status: settled|estimated|pending|rejected, prompt_tokens, cached_tokens, completion_tokens, reasoning_tokens, cost_usd, credits}], has_more, next_before, totals:{requests, prompt_tokens, completion_tokens, cost_usd, credits}}. Rejected requests cost nothing. estimated means the model accepted a request but no usage receipt arrived (a timeout or a broken stream): it is charged an estimate, the prompt at three bytes per token plus the output that was sent to you, never more than the hold. ## GET /v1/usage/summary Query: days (1-90, default 30; today counts as one), key_id (a key id, or "current" for the calling key). Returns {object:"usage.summary", days, from, to, totals:{requests, prompt_tokens, completion_tokens, cost_usd, credits}, by_day:[{date, ...the same five figures}], by_model:[{model, ...}], by_key:[{api_key_id, ...}]}. Days are UTC; a day without a charged request is not listed. Only charged requests count (settled and estimated). by_model and by_key are sorted by cost, highest first. ## Errors OpenAI format: {"error":{"message","type","param","code"}}. - 400 invalid_request_error (invalid_request, invalid_json) - 401 authentication_error (missing_api_key, invalid_api_key, revoked_api_key, expired_api_key: the key passed its end date, create a new one or move the date in the app) - 402 insufficient_quota (insufficient_quota: add credits in the app; request_exceeds_balance: this request could cost more than the balance or than what is left of the key's spending limit, send a shorter prompt or add credits; key_spending_limit_reached: this key has spent its limit, raise it in the app or use another key; a limit per day or per month starts again at 00:00 UTC, and the message says on which date) - 404 invalid_request_error (model_not_found, character_not_found) / not_found_error (unknown_endpoint) - 413 request body over 5 MB - 429 rate_limit_error (rate_limit_exceeded: 60 requests/minute per account, see Retry-After; model_overloaded) - 502/504 server_error (search_unavailable: the search could not run, nothing charged), 503 service_unavailable (backend_not_configured, backend_unavailable): retry with backoff Mid-stream failures arrive as a final "data: {"error":{...}}" event before [DONE]. ## Rate limits 60 requests per minute per account, shared by all its keys (fixed one-minute window). Headers: x-ratelimit-limit-requests, x-ratelimit-remaining-requests, x-ratelimit-reset-requests. Each successful chat and search response carries X-Request-Id, matching the usage record id. ## MCP server (remote, recommended) Endpoint: POST https://candorai.dev/mcp, MCP Streamable HTTP transport (stateless; protocol versions 2025-11-25, 2025-06-18, 2025-03-26). Auth: "Authorization: Bearer sk-unc-...", same keys, billing, holds, usage rows and rate limit as /v1. Without a valid key: HTTP 401 with WWW-Authenticate. Tools: - chat {prompt | messages, model? (candor-fast | candor-classic | candor-smart, default candor-classic), system?, max_tokens?, temperature?}: returns the reply text plus structuredContent {model, reply, finish_reason, usage, request_id}. Errors such as an empty balance come back as isError results with the reason. - list_models {}: models and USD prices per 1M tokens. - candor_balance {}: paid credits available (USD) and whether a request can run. - web_search {query}: search the web and return the results (title, address, excerpt). Charged like POST /v1/web/search. The chat tool also takes web_search: true to search before answering. - list_characters {category?, search?}: the characters that can be played, with their ids. Pass one to chat as "character" and the reply comes from that character. Claude Code: claude mcp add --transport http candor https://candorai.dev/mcp --header "Authorization: Bearer $CANDOR_API_KEY" Cursor (~/.cursor/mcp.json): { "mcpServers": { "candor": { "url": "https://candorai.dev/mcp", "headers": { "Authorization": "Bearer sk-unc-..." } } } } Windsurf (~/.codeium/windsurf/mcp_config.json): { "mcpServers": { "candor": { "serverUrl": "https://candorai.dev/mcp", "headers": { "Authorization": "Bearer sk-unc-..." } } } } Codex (~/.codex/config.toml; export CANDOR_API_KEY first): [mcp_servers.candor] url = "https://candorai.dev/mcp" bearer_token_env_var = "CANDOR_API_KEY" Claude Desktop (claude_desktop_config.json, via the mcp-remote bridge): { "mcpServers": { "candor": { "command": "npx", "args": [ "-y", "mcp-remote", "https://candorai.dev/mcp", "--header", "Authorization:${CANDOR_AUTH}" ], "env": { "CANDOR_AUTH": "Bearer sk-unc-..." } } } } ## Guides by tool Every tool that speaks the OpenAI format needs three values: the base URL https://candorai.dev/v1, a key, and a model id (candor-fast, candor-classic, candor-smart). GET /v1/models needs no key, so a tool that lists models can load them. Postman: import https://candorai.dev/postman.json and set the collection variable CANDOR_API_KEY. Cline and Roo Code (VS Code) (Settings > API Provider > OpenAI Compatible): Base URL: https://candorai.dev/v1 API Key: sk-unc-... Model ID: candor-smart Continue (VS Code, JetBrains) (~/.continue/config.yaml, under models): models: - name: Candor Smart provider: openai model: candor-smart apiBase: https://candorai.dev/v1 apiKey: sk-unc-... aider (your shell): export OPENAI_API_BASE=https://candorai.dev/v1 export OPENAI_API_KEY=$CANDOR_API_KEY aider --model openai/candor-smart Open WebUI (Admin Panel > Settings > Connections > OpenAI API > add a connection): URL: https://candorai.dev/v1 Key: sk-unc-... LibreChat (librechat.yaml (set CANDOR_API_KEY in .env)): endpoints: custom: - name: "Candor" apiKey: "${CANDOR_API_KEY}" baseURL: "https://candorai.dev/v1" models: default: ["candor-fast", "candor-classic", "candor-smart"] fetch: true titleConvo: true titleModel: "candor-fast" SillyTavern (API Connections > API: Chat Completion > Chat Completion Source: Custom (OpenAI-compatible)): Custom Endpoint (Base URL): https://candorai.dev/v1 Custom API Key: sk-unc-... Model ID: candor-classic n8n (Credentials > OpenAI, then pick the credential in an OpenAI Chat Model node and type the model id): API Key: sk-unc-... Base URL: https://candorai.dev/v1 Model: candor-smart (by ID) LiteLLM (Python) (your code): # pip install litellm import os from litellm import completion reply = completion( model="openai/candor-smart", api_base="https://candorai.dev/v1", api_key=os.environ["CANDOR_API_KEY"], max_tokens=300, messages=[{"role": "user", "content": "Hello"}], ) print(reply.choices[0].message.content) ## OpenAI-compatible SDKs Python (openai): # pip install openai import os from openai import OpenAI client = OpenAI(base_url="https://candorai.dev/v1", api_key=os.environ["CANDOR_API_KEY"]) stream = client.chat.completions.create( model="candor-fast", messages=[{"role": "user", "content": "Write a limerick about the sea."}], max_tokens=512, stream=True, ) for chunk in stream: if chunk.choices and chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="") JavaScript (openai): // npm install openai import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://candorai.dev/v1", apiKey: process.env.CANDOR_API_KEY }); const completion = await client.chat.completions.create({ model: "candor-smart", messages: [{ role: "user", content: "Explain quicksort in three sentences." }], max_tokens: 512, }); console.log(completion.choices[0].message.content); LangChain: # pip install langchain-openai import os from langchain_openai import ChatOpenAI llm = ChatOpenAI(model="candor-classic", max_tokens=512, base_url="https://candorai.dev/v1", api_key=os.environ["CANDOR_API_KEY"]) print(llm.invoke("Give me three blunt tips for a first marathon.").content) Vercel AI SDK: // npm install ai @ai-sdk/openai-compatible import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; import { generateText } from "ai"; const candor = createOpenAICompatible({ name: "candor", baseURL: "https://candorai.dev/v1", apiKey: process.env.CANDOR_API_KEY }); const { text } = await generateText({ model: candor("candor-fast"), maxOutputTokens: 512, prompt: "Summarise the plot of Hamlet in two lines." }); console.log(text);