Use your Cursor subscription as an OpenAI-compatible API.
Two other projects share this name. Both wrap the free chatbot on Cursor's website — 7836246/cursor2api and gopkg-dev/cursor2api. This one is different: it uses your own paid subscription through Cursor's agent backend. See comparison.
It talks to Cursor's own agent.v1.AgentService backend directly — Connect-RPC over
HTTP/2 with protobuf — and exposes it as /v1/chat/completions. No cursor-agent
CLI subprocess, no headless browser, no scraping.
your app ──► /v1/chat/completions ──► cursor2api ──► api2.cursor.sh
(OpenAI shape) (protocol bridge) (your subscription)
| Streaming (SSE) | ✅ |
Real tool_calls with decoded arguments |
✅ |
| Multi-turn agent loops (tool results replayed) | ✅ |
| Caller controls the tool set — Cursor's native tools hidden | ✅ |
| 200+ models, fetched live from your account | ✅ |
| Multi-account pool, round-robin + cooldown | ✅ |
| Automatic token refresh | ✅ |
Client-supplied system prompt |
git clone https://github.com/AstroQore/cursor2api.git
cd cursor2api
mkdir -p data
# 1. sign in — prints a URL, open it in any browser and approve
docker compose run --rm cursor2api node scripts/login.mjs --out /data/accounts.json
# 2. start
docker compose up -d
# 3. use it
curl http://127.0.0.1:8790/v1/chat/completions \
-H 'content-type: application/json' \
-d '{"model":"cursor-claude-sonnet-5-thinking-high","messages":[{"role":"user","content":"hi"}]}'docker compose up -d pulls the published image, or builds from source if the
registry copy is unavailable — either way the command is the same.
docker run --rm -it -v "$PWD/data:/data" ghcr.io/astroqore/cursor2api:latest \
node scripts/login.mjs --out /data/accounts.json
docker run -d --name cursor2api -p 8790:8790 -v "$PWD/data:/data" \
ghcr.io/astroqore/cursor2api:latestnpm install
npm run login # or: npm run import-local (reads a local Cursor install)
npm run build:bundle
node dist/index.cjsTwo ways to get a token into accounts.json:
npm run login — the browser deep-link flow Cursor's own CLI uses. It prints a
cursor.com/loginDeepControl URL, you approve it in any browser on any machine, and
the tokens land in accounts.json. Works on a headless VPS. Add --proxy socks5://…
if the box needs one to reach cursor.com.
npm run import-local — reads cursorAuth/accessToken from an existing Cursor
desktop install (state.vscdb). Handy on your own laptop.
Both write mode-0600 files and print only metadata, never the tokens. For several
accounts, run either with --merge --label some-name; the pool round-robins over them
and cools an account down for 60s after a failure.
Access tokens last about two months and are refreshed automatically via the refresh token, so a one-time login is normally all you need.
Model names are the upstream ids with a cursor- prefix (CURSOR_DIRECT_MODEL_PREFIX=
to disable). GET /v1/models lists whatever your account can actually reach.
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8790/v1", api_key="unused")
print(client.chat.completions.create(
model="cursor-claude-sonnet-5-thinking-high",
messages=[{"role": "user", "content": "Explain HTTP/2 in one sentence."}],
).choices[0].message.content)Tool calling works the normal way — declare tools, get tool_calls back, send the
result as a role: "tool" message:
resp = client.chat.completions.create(
model="cursor-claude-opus-5-thinking-high",
messages=[{"role": "user", "content": "Weather in Osaka?"}],
tools=[{"type": "function", "function": {
"name": "get_weather",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}},
}}],
)
resp.choices[0].message.tool_calls[0].function.arguments # '{"city":"Osaka"}'Anything that speaks OpenAI works: Cline, Roo, Continue, LobeChat, one-api, your own scripts. If you aggregate several subscription providers behind CLIProxyAPI, see docs/deploy-behind-cliproxyapi.md.
Several projects named cursor2api wrap the chatbot on Cursor's marketing/docs site
(cursor.com/learn). That endpoint is anonymous and free, which makes those projects
easier to start with — and gives them a completely different set of problems.
| free-chatbot wrappers | this project | |
|---|---|---|
| Where the capability comes from | public chatbot on cursor.com | your paid Cursor subscription |
| Authentication | none | your Cursor login (PKCE), auto-refreshed |
| Anti-bot handling | required — Chrome TLS fingerprinting plus a side service that deobfuscates Cursor's rotating JS to mint tokens | none; this is an authenticated client of a real API |
| Models | whatever the site widget exposes | 200+, read live from your account |
tool_calls |
no | yes, with tool-result replay |
| Quota | shared/free, rate limited | your own subscription |
| Breaks when | Cursor rotates the anti-bot script | Cursor changes the agent protocol or version-gates clients |
A note on whether those still work, since people ask: as of August 2026,
gopkg-dev/cursor2api (Go, MIT) and its
required companion x-is-human-api were both last pushed in October 2025, and its
open issue #2 (March 2026) reports that the anti-bot JS URL its setup depends on no
longer exists. Treat it as unmaintained unless that changes. Design-wise it is
solid — SSE streaming, client-cancel propagation, TLS fingerprint spoofing — the
fragility is inherent to scraping an anti-bot-protected public endpoint, not a flaw
in the code.
If all you want is a free tap for casual chat and you do not have a Cursor subscription, a free-chatbot wrapper is the right tool. If you pay for Cursor and want your quota as a real API with tool calling, use this.
All env vars, all optional.
| Variable | Default | Purpose |
|---|---|---|
CURSOR_DIRECT_HOST / _PORT |
127.0.0.1 / 8790 |
Listener (the image sets host to 0.0.0.0) |
CURSOR_DIRECT_API_KEY |
— | Require Authorization: Bearer <key>. Set this if the port is reachable from anywhere but localhost. |
CURSOR_DIRECT_AUTH_FILE |
./accounts.json |
Account/token store |
CURSOR_DIRECT_MODEL_PREFIX |
cursor- |
Prefix on exposed model names |
CURSOR_ALLOWED_NATIVE_TOOLS |
mcp_tool_call |
Which of Cursor's native tools the model may see. * keeps all 41. |
CURSOR_DIRECT_PROXY_URL |
— | SOCKS5 for all upstream traffic, e.g. socks5://127.0.0.1:1080 |
CURSOR_DIRECT_CLIENT_VERSION |
cli-2026.08.11-… |
Bump if Cursor version-gates old clients |
CURSOR_DIRECT_SYSTEM_AS_HISTORY |
true |
How system messages are delivered (see below) |
CURSOR_DIRECT_TIMEOUT_MS |
300000 |
Upstream turn timeout |
Cursor's backend is an agent protocol, not a model API, so the bridge has to do real translation rather than forwarding:
- History travels out-of-band. Messages are serialized to JSON blobs keyed by sha256 and only the ids go in the request; the server then pulls the contents back over a KV channel mid-stream.
Runis a bidirectional stream. One turn is:run_request→ server fetches blobs →request_contexthandshake (where the caller's tools are declared) → text deltas →turn_ended.- Tool calls are bridged across two different models of the world. Cursor drives
tools server-side over an exec channel; OpenAI clients execute tools themselves. So
when the model asks for a tool, the turn is ended with
finish_reason: tool_callsand the caller'srole: "tool"reply is replayed as history on the next request. x-cursor-agent-allowed-toolsis what hides Cursor's 41 built-in tools, so the model sees exactly the tools your client declared. This header is only reachable on the direct protocol — the CLI does not forward its--exclude-toolsflag on the normal run path, which is the main reason this project exists.
Two wire details that cost time if you're building something similar: streaming uses
application/connect+proto with 5-byte framing while unary calls use
application/proto with a bare body (mixing them returns a bodyless 415), and tool
arguments arrive as map<string, bytes> where each value is a serialized
google.protobuf.Value.
Client system prompts are best-effort. Cursor's server composes the model prompt
itself. A real {"role":"system"} entry is fetched from the blob store and then
discarded — a canary string placed there never reaches the model. Only conversation
history is delivered, so your system message is sent as the opening exchange,
labelled as configuration from the gateway operator. In practice the model follows it;
sometimes it declines part of it, and that answer is passed back to you unchanged. The
proto field intended for this (custom_system_prompt) is documented upstream as
"Allowlisted for specific teams only".
Cursor's coding-assistant persona is always present. Even with an empty request context the model still identifies as a Cursor assistant.
prompt_tokens is always 0. The wire protocol reports a single token counter with
no prompt/completion split.
todo_write stays visible. It is resolved server-side and ignores the tool
allow-list. Harmless — it never asks the client to execute anything.
This project stands on other people's reverse-engineering. Specifically:
- can1357/oh-my-pi —
proto/agent.protois vendored from its Cursor provider. It also established thatconversation_state.root_prompt_messages_jsonis the channel the server uses to build the model prompt, and that tokens are refreshed at/auth/exchange_user_api_key. By far the biggest debt. - AmazingAng/auth2api — the
loginDeepControl+auth/pollPKCE login flow thatscripts/login.mjsfollows. - sdsdsdsdsdsihdkjsdjl/cursoride2api
— its endpoint catalogue documents which of the legacy
aiserver.v1RPCs are dead. - eisbaw/cursor_api_demo — early reverse engineering of the backend endpoints and the client checksum.
- burpheart/cursor-tap — gRPC traffic analysis tooling and notes.
- anyrobert/cursor-api-proxy — the CLI-wrapper approach that came first; this project is what you build after hitting its ceiling.
- gopkg-dev/cursor2api and
7836246/cursor2api — the free-chatbot
wrappers that mapped out the OpenAI-shaped surface people expect from a
cursor2api, and a useful reference for SSE and cancellation handling even though the upstream they target is a different one. - router-for-me/CLIProxyAPI — the "OAuth login → usable API" pattern that this follows, and a good place to put this behind if you aggregate several providers.
AGPL-3.0. Note the network clause: if you run a modified version of this service and let other people reach it over a network, you have to offer them the source of your modified version. Running it unmodified, or purely for yourself, carries no such obligation.
This is an unofficial client for a private, undocumented API. It is not affiliated with or endorsed by Anysphere. It can break whenever Cursor changes its client contract, and using it may conflict with Cursor's terms of service — check them, and use your own account. Traffic counts against your own subscription quota.