English | 中文
devin-2api is an unofficial protocol adapter that exposes the models available to your Devin account (app.devin.ai) behind OpenAI- and Anthropic-compatible endpoints — so standard clients (Codex, Claude Code, any SDK) can call them through familiar APIs.
Disclaimer: this project is not affiliated with or endorsed by Cognition. It authenticates with your own Devin credentials (session token or durable platform key) against an internal RPC surface. It is intended for personal use with your own account; you are responsible for complying with Devin's terms of service.
- Three API surfaces on one upstream —
POST /v1/responses(OpenAI Responses, incl. a WebSocket transport with multi-turn sessions for Codex-style clients),POST /v1/chat/completions(OpenAI Chat),POST /v1/messages(Anthropic Messages) - Streaming and non-streaming responses (typed SSE / JSON)
- Reasoning that round-trips — thinking signatures are preserved and replayed across turns:
encrypted_contentreasoning items on Responses,redacted_thinkingon Anthropic,reasoning_contenton Chat - Tool calling — custom/freeform tool calls (e.g.
apply_patch) round-trip untouched; tool names andtool_choiceare validated locally; strict call↔result re-pairing matches what upstream enforces. Server-managedweb_searchdeclarations are executed through the upstream search RPC and returned as nativeweb_search_call/server_tool_useitems — a Claude Code WebSearch side request short-circuits into a single managed search - Image, document, and video inputs —
input_image,input_file/file/document, andvideo/video_url/input_videoparts decode on all three surfaces (Anthropicimage/document/videoblocks included). Per-model capability flags gate them locally before the wire, so an incapable model fails fast instead of silently dropping the attachment; video is frames only, no audio track - Upstream stream recovery — expired tokens are reloaded from the credentials source, pre-content upstream failures (transport breaks, silent stalls, empty replies) are retried transparently, and early failures surface as real HTTP errors instead of SSE errors after a committed
200 - Detached completion cache — a client disconnect doesn't kill the upstream stream: it keeps running server-side into a completion cache, and a semantically identical retry re-attaches — completed entries replay instantly, running ones replay the buffered prefix then follow live. Entries persist across restarts
- Multi-account upstream pool —
devin.accountslanes each carry their own credentials, rate gate, and quota tracking. Session affinity keys (X-Claude-Code-Session-Id,X-Session-ID/X-Session-Affinity/X-Conversation-Id/X-Thread-Idheaders,metadata.user_id,prompt_cache_key/user) pin a conversation to a lane, upstream failures fail over to a healthier sibling, and low weekly quota demotes a lane for new sessions. Lanes are managed live from/web/accounts.html— an empty pool is a legal starting state - Rate-limit gate — upstream
resource_exhaustedtrips a local cooldown latch: queued requests wait briefly then fast-fail429+Retry-Afterinstead of hammering a limited upstream, drip-released probes detect recovery, and latch state persists across restarts (indevin-2api.db). An optionalmax_rpmtoken bucket shapes outbound pressure before the latch ever trips - Optional prefix warming — replays retained session request bodies on a cadence to renew the upstream prompt cache, so a subagent resuming after a long wait doesn't pay a cold prefill (
devin.warm_prefix_*, off by default; seedocs/upstream-cache.md) - Normalized error contract — upstream error codes map to proper HTTP status and per-protocol error types; rate limits become
429+Retry-After; with request logging on (debug.enabled, on in the shippedconfig.example.yaml) every request carriesX-Request-Id/debug_refidentifying its debug record /v1/modelscapability flags — context window plus tool/thinking/image/document/video support surfaced from the upstream model catalog;devin.aliasesentries appear withalias_of, and the panel model registry can disable or redirect individual names- Admin panel at
/web— request browser, usage/cost aggregation, quota tracking, process metrics, per-request debug payloads, downstream token management, pool lane control, model registry, a redacted config view with hot reload for most fields, and one-click self-update on managed installs - Single static binary — public Docker image on GHCR
devin-2api authenticates to Devin with one credential per upstream account in devin.accounts — the pool model covers single-account setups too (accounts is just a one-entry list), and an empty pool is legal: boot first, add accounts later from the panel (/web/accounts.html) without a restart. Each entry takes one (or more) of three credential sources:
api_key(recommended) — a durable Devin platform key (cog_...) issued at app.devin.ai → Settings → API keys. It carries no built-in expiry (valid until revoked): whenever upstream reportsunauthenticatedthe lane mints itself a fresh session token from it, so anapi_key-only account self-heals indefinitely with zero maintenance.token— a literal Devin session token (devin-session-token$...). Works directly, but session tokens carry a server-side TTL — once one dies, the lane can only recover if another source yields a fresh credential. Where to get one: it's thewindsurf_api_keyvalue insidecredentials.tomlafterdevin auth login(see below), or extractable from the Devin app's local state (macOS snippet below).credentials_file— points at the Devin CLI credential file (~/.local/share/devin/credentials.tomlon macOS/Linux;%APPDATA%\devin\credentials.tomlon Windows), which holds a session token the CLI renews on its own logins — re-reading the file follows CLI renewals automatically. ThedevinCLI ships inside the Devin desktop app underresources/app/extensions/windsurf/devin/bin/— e.g.C:\Program Files\Windsurf\on Windows,/usr/share/devin-desktop/from the Linux.deb— rundevin auth login, finish the browser sign-in, and the file above appears.
Combining sources on one account is fine — e.g. api_key + token: the literal token serves until it dies, then the durable key mints a replacement.
On macOS you can also extract a session token from the Devin app's local state:
sqlite3 ~/Library/"Application Support"/Devin/User/globalStorage/state.vscdb \
"SELECT json_extract(value, '$.apiKey') FROM ItemTable WHERE key='windsurfAuthStatus';"When upstream answers unauthenticated, the lane re-resolves that account's credential — credentials_file re-read (follows CLI renewals) → row/config token if it changed → api_key mint — so every source except a bare literal token heals the proxy without a restart.
cp config.example.yaml config.yamlEdit config.yaml and declare your account under devin.accounts (starting from config.example.yaml, a single {name, token} entry is enough — the base URL and model are pre-filled as examples).
Five install routes, all ending at the same binary — they differ in who manages the process and how upgrades work.
curl -sSL https://raw.githubusercontent.com/WncFht/devin2api/main/scripts/install.sh | bashOne command, no clone, no root. install.sh fetches the deploy pipeline at the target tag and hands off to the platform deploy script — the binary lands in ~/.local/bin, config and state in the platform dirs (option B's layout table), and the service runs under systemd --user (Linux) or launchd (macOS) with a zero-downtime REUSEPORT handoff on every restart. Subcommands: install (default; -v <tag> pins a release), upgrade, rollback <tag>, status, list-versions, uninstall (removes service + binary, keeps config and logs). On Windows use option B's PowerShell script or option C instead.
On first run config.yaml is generated from config.example.yaml with a random dashboard.password, and you're prompted for the Devin token (left empty the pool starts empty — add accounts later from the panel); downstream /v1 tokens are created in the panel (/web/tokens.html), never in config. To preset values, cp config.example.yaml config.yaml and edit beforehand.
On Linux, run loginctl enable-linger $USER if the service must outlive your login session.
The same managed service as option A, driven from a clone — useful when you want the repo on disk, since the scripts treat it as home (sync config.yaml into the platform config dir, keep a logs symlink in the repo pointing at the state dir):
git clone https://github.com/WncFht/devin2api && cd devin2api
bash scripts/deploy/deploy-linux.sh --release latest # macOS: scripts/deploy/deploy.sh--release latest fetches a sha256-verified prebuilt binary, then the script polls /healthz until the new version answers and probes GET /v1/models to confirm upstream auth works. --check reports installed/running/latest versions; --uninstall removes the service and binary while keeping config and logs; --no-restart swaps the binary without restarting. First-run config generation works the same as option A.
| Platform | Supervisor | Layout | Script |
|---|---|---|---|
| macOS | launchd agent | bin ~/.local/bin · config+state ~/Library/Application Support/devin-2api |
scripts/deploy/deploy.sh |
| Linux | systemd --user |
bin ~/.local/bin · config ~/.config/devin-2api · state ~/.local/state/devin-2api |
scripts/deploy/deploy-linux.sh |
| Windows | none — console, or NSSM / Task Scheduler | exe %LOCALAPPDATA%\Programs\devin-2api · config %APPDATA%\devin-2api · state %LOCALAPPDATA%\devin-2api |
scripts/deploy/deploy-windows.ps1 |
On Windows deploy-windows.ps1 -Release latest generates config.yaml bound to loopback on a free port (avoids the firewall prompt and a bare exposure) and starts the instance in its own console window — run it from a local interactive session, not over SSH (the job object kills the instance when the session ends).
Assets on Releases are named devin-2api-{darwin,linux}-{amd64,arm64}; Windows ships devin-2api-windows-{amd64,arm64}.zip bundles (exe + config.example.yaml + LICENSE). checksums.txt is attached for verification:
# Linux shown; on macOS use devin-2api-darwin-arm64 or -darwin-amd64
curl -fLO https://github.com/WncFht/devin2api/releases/latest/download/devin-2api-linux-amd64
curl -fLO https://github.com/WncFht/devin2api/releases/latest/download/checksums.txt
sha256sum -c checksums.txt --ignore-missing # expect: devin-2api-linux-amd64: OK
chmod +x devin-2api-linux-amd64
./devin-2api-linux-amd64 -config config.yamlOn Windows: unzip, edit config.yaml (an account may carry only credentials_file — step 1 covers the Windsurf-bundled devin.exe that produces the credential file), then run devin-2api.exe -config config.yaml in a console. Ctrl+C triggers the same graceful drain; closing the window and taskkill /F do not — Windows offers no graceful kill for console processes.
Path resolution: config via -config flag → DEVIN2API_CONFIG → ./config.yaml → the platform default in option B's table; state via -state-dir → DEVIN2API_STATE_DIR → the platform default.
The image is published on GHCR:
docker run --rm -p 8080:8080 \
-v "$PWD/config.yaml:/app/config.yaml" \
-v devin2api-state:/app/state \
-e DEVIN2API_STATE_DIR=/app/state \
ghcr.io/wncfht/devin2api --config /app/config.yamlThe state volume keeps devin-2api.db (downstream tokens, request logs, quota samples) across container restarts — without it each run starts with an empty store, meaning open /v1 access.
Generated proto bindings are committed under outputs/devin-proto-go, so a clone builds with no extra toolchain:
go run ./cmd/devin-2api -config config.yamlManaged installs (options A–B) can update themselves from the panel — /web/settings.html carries a version-update card: check → download → sha256 verify → swap → restart across the same REUSEPORT handoff, so nothing drops and the panel polls progress across the process switch. POST /admin/update (with /admin/update/check, /admin/update/status, /admin/update/rollback) exposes the same flow to scripts; rollback replays the swap against the .backup binary the update left behind — no download. Everywhere else — a manually started binary, a Windows console, Docker — the endpoints answer 501: re-run your install route instead (deploy script, new download, or a fresh image pull).
curl http://localhost:8080/healthz
# {"status":"ok","version":"...","uptime_seconds":12,"debug_logging":false}Note:
/v1/*endpoints are gated by the token store (theauth_tokenstable indevin-2api.db, state dir): clients sendAuthorization: Bearer <token>orX-Api-Key: <token>matching an active token row. Tokens are managed only in the panel (/web/tokens.html) — plaintext is shown once at creation and the store keeps hashes; config carries no data-plane credential. An empty store means open access — only bind beyond loopback if the store gates access, or you are handing out your Devin quota to the network. Tokens carry an optionalclass(fgdefault /bgfor unattended batch traffic) that changes rate-gate admission — seedocs/gate-classes.md.
Endpoints:
POST /v1/responses— OpenAI Responses (GETon the same path negotiates WebSocket transport)POST /v1/chat/completions— OpenAI Chat CompletionsPOST /v1/messages— Anthropic MessagesGET /v1/models,GET /v1/models/{model}— upstream model catalog with capability flagsGET /web/*— admin panel (request browser, usage, quota, process stats);dashboard.passwordprotects it
The proxy is stateless: every HTTP request must carry the full conversation — previous_response_id is rejected with an explicit error rather than silently dropping context (there is no server-side response store; the response reports store=false). Over the WebSocket transport, multi-turn sessions are maintained per connection and incremental inputs are expanded into full transcripts transparently.
Call http://localhost:8080/v1/responses with your OpenAI Responses API client.
Non-streaming:
curl http://localhost:8080/v1/responses \
-H "Content-Type: application/json" \
-d '{
"model": "swe-2-max",
"input": "Hello"
}'Streaming (SSE):
curl -N http://localhost:8080/v1/responses \
-H "Content-Type: application/json" \
-d '{
"model": "swe-2-max",
"input": "Hello",
"stream": true
}'The request body follows the OpenAI Responses API (input, instructions, tools, stream, …). Anthropic Messages clients call /v1/messages instead:
curl http://localhost:8080/v1/messages \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "swe-2-max",
"max_tokens": 256,
"messages": [{"role": "user", "content": "Hello"}]
}'See the Contributing guide for the exact subset of fields supported per surface.
Configuration is a YAML file loaded once at startup. Unknown fields are rejected.
server:
listen: ":8080"
devin:
base_url: "https://server.codeium.com"
accounts:
- name: "main"
token: "devin-session-token$..."
model: "swe-2-max"
debug:
enabled: false
dashboard:
password: "" # /web login; empty = open
# Downstream /v1 tokens live only in the panel (/web/tokens.html) — config
# carries no data-plane credential. An empty token store means open access.Notes:
- tokens are never written to logs (redacted as
<redacted>); - with no accounts configured the pool is empty:
/v1requests fail fast withunavailable— add an account from the panel (/web/accounts.html) ordevin.accounts+ reload, no restart needed either way; config.yamlis gitignored — keep real tokens out of git anyway; pre-commit runs gitleaks to catch committed secrets.
Every /v1/* response carries an X-Request-Id header (error bodies also carry debug_ref) naming the request's debug record — GET /admin/debug-logs/{id}/file/error.json shows the first failure point, and logs/stderr.log in the state dir has one summary line per request. Common symptoms:
401— no matching token in the store (create one in/web/tokens.html), or upstream rejected the account credential; the lane re-resolves it and retries once — check lane status in/web/accounts.htmlunavailable— the account pool is empty; add an account in/web/accounts.html, or viadevin.accounts+ config reload429— the local rate gate fast-failed (Retry-Aftersays when to retry) or upstream is rate-limiting; theerror_stagecolumn in thelogstable (rate_gatevsdevin_connect) distinguishes them- SSE stalls near ~300s — the client's own total timeout, not the proxy's; a disconnected stream keeps running server-side and an identical retry replays it from the completion cache
The full symptom → layer → fix table lives in docs/upstream-debug-playbook.md(中文).
- User guide (install, clients, configuration, troubleshooting): wncfht.github.io/devin2api
- Architecture, supported API fields, proto extraction, and other technical details: Contributing guide
- Upstream protocol reverse-engineering notes, client setup, debugging playbook, deployment & toolchain: docs/(中文)
- License: MIT
This project builds on leookun/devin-2api — thanks to the original authors for their work.

