Client libraries
Cursor
Our own chat models in Cursor, through the editor's OpenAI base URL override — the exact settings, what is known to work and what is not yet, and what each error means.
Cursor can send its chat requests to any OpenAI-compatible base URL, and ours is one. Its chat
modes send tools on every request, so use one of our own chat models — glm-5-3-flash,
deepseek-v4-1-flash or kimi-k3 — which take tools. The catalogue's language models
(auray-serverless/model/…) do not, and a request to one that carries tools is refused.
Cursor's requests come from Cursor's servers, not from your machine, so nothing needs to be reachable on your side — and it also means Cursor holds your key and sends it for you.
#1. A key for Cursor, and only for Cursor
- In the desktop, open Account → API Keys and create a key used by nothing else. Untick every
scope except
text:write: Cursor needs nothing more, and a key that can do little else is worth less to anyone who gets hold of it. Little is not nothing:text:writealso reaches the catalogue's language models, and uploads — and reads back — the account's files of the kinds they take: images, video, audio and documents. (A key minted with the older scope names holdschat:writeinstead, which works the same for Cursor and reaches no upload.) - Set its monthly credit ceiling to what you would accept losing if the key leaked — but above
what a working session costs: an agent sends a request for every step it takes, each one reserves
credits before it runs, and a request that would take the key past its ceiling is answered
429. - Press Copy in the sheet that shows the new key. The key starts with
auray_sk_and is 71 characters; it is shown once and never again.
Revoke the key in Account when you stop using Cursor with us, or the moment you think it has leaked; nothing else uses it, so nothing else breaks.
The line Account lists for each key afterwards — ID and twelve characters — is the key's
ID, not the key. Pasted into Cursor it is refused 401 invalid_api_key, which Cursor shows as
"Unauthorized User API key". If all you have is the ID, create a new key.
Completions need a plan whose keys may run them. On a plan where they may not, every request is refused
403 plan_has_no_api_generate. Completions spend the plan's
interactive bucket, and GET /v1/plans shows it closed by either of these:
limits.api.may_generateis falselimits.api.interactive.burstis 0
Check the key before you open Cursor, with the key in an environment variable rather than in the command:
curl -s https://api.auray.ai/openai/v1/chat/completions \
-H "Authorization: Bearer $AURAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"glm-5-3-flash","messages":[{"role":"user","content":"Say OK."}]}'A 200 with an answer means the key and the plan are right. 401 is the key; 403 is the plan.
#2. Cursor's settings
In Cursor, open Settings → Models:
- OpenAI API Key — paste the whole key, then switch its toggle on. Saving the key and turning it on are two separate actions.
- Override OpenAI Base URL —
https://api.auray.ai/openai/v1, exactly: no trailing slash, and not/chat/completions— Cursor adds that itself, and a doubled one answers404. - AWS Bedrock — off. With it on, Cursor sends requests without your key.
- Add model — type the name exactly:
glm-5-3-flash,deepseek-v4-1-flashorkimi-k3, and select it in the chat.
Picking a thinking level or Fast in Cursor's model picker is fine. Cursor adds
-thinking-<level> and -fast to the name it sends, and we read glm-5-3-flash-thinking-high as
glm-5-3-flash at its deepest reasoning; -fast changes nothing.
Avoid naming a custom model after one of Cursor's own — a name with gpt-5, claude or gemini in
it makes Cursor build its request for that model's own service instead.
#What works, and what is not known yet
- Ask sends OpenAI chat completions with tools, and that is what our own chat models answer:
its calls come back as
tool_calls, Cursor runs them, and the results go back in its next request. Acustomtool such as Cursor's patch tool is offered to the model as a function of one string and returned as a custom call. - Agent works the same way when Cursor sends it as chat completions. Cursor's Agent mode has
also been seen sending OpenAI's newer Responses format to a custom base URL —
inputwhere chat completions hasmessages. Sent to/responses, that shape is answered as chat completions are; sent to/chat/completions, it is400 messages_required. If Agent shows that error, use Ask. - Tab, Auto, background agents and the Cursor CLI never use a custom base URL; they run on Cursor's own models whatever you set here.
Reasoning comes back as reasoning_content, beside the answer, and is billed as the output tokens
it is. If a client sends it back on the assistant message while it is still working on the same
question, it reaches the model for that exchange; from an earlier question it is dropped.
A request has no per-message length limit, so an attached file inside your message is fine; the
whole request is capped at one mebibyte. A long conversation is held to your plan's history budget,
counted in bytes, not tokens — each message as the JSON it is sent as, plus 64, and 128 for the
request (GET /models prints yours as history_budget_bytes). Cursor's own instructions are never
counted; the current exchange — an agent task and every step of it — counts first and is always sent
whole, and older parts are kept, newest first, while the rest fits, the others left out a whole
exchange at a time. An answer that left earlier messages out says how many in its
X-Auray-History-Trimmed header. Every request is reserved at
the size of everything it sends, so a long agent session reserves more per step than a short
question, and is settled down to what the model reported.
#What Cursor's errors mean
Cursor turns every 401 into its own "Unauthorized User API key", and every 429 or 5xx — and a
request that got no answer — into "User API Key Rate limit exceeded", whatever we said. Other
refusals carry our code; each is explained on the errors page.
| We answered | It means | Do this |
|---|---|---|
401 — Cursor says "Unauthorized User API key" | No key, the key's ID, or a key that is wrong or revoked | Paste the whole key and turn its toggle on; turn Bedrock off. |
403 plan_has_no_api_generate | Your plan's keys may not run completions: in GET /v1/plans, its limits.api.may_generate is false or its limits.api.interactive.burst is 0 | Choose a plan with may_generate true and interactive.burst above 0, in Account → Plan & Billing. |
429 — Cursor says "User API Key Rate limit exceeded" | Too many requests a minute, an empty wallet, or the key's monthly credit ceiling | Wait a minute; if it persists, top up or raise the key's ceiling. |
400 unknown_model | The name is not one we serve | Use one of the three names above, exactly. |
400 tools_unsupported | The model is a catalogue model, or a tool is of a hosted type | Use one of our own chat models. |
400 messages_required | Cursor sent the Responses format (input), not chat completions | Use Ask; see above. |
400 tools_rejected | The model's server refused a tool definition as sent | Check the tool's schema; nothing was charged. |
Every answer carries an X-Request-Id header, which is what to quote if you write to us.