Developers / API reference
Docs & help
From authentication to project-context injection, model routing and streaming: everything a request goes through inside DeepCodex, in one place.
https://www.deepcodex.cn/v1Endpoints
| Protocol | Method | URL | Status | Streaming | Typical use |
|---|---|---|---|---|---|
| OpenAI Chat Completions | POST | https://www.deepcodex.cn | Supported | Streams | SDKs / curl / most terminals |
| OpenAI Responses | POST | https://www.deepcodex.cn | Supported | Streams | Codex CLI |
| Anthropic Messages | POST | https://www.deepcodex.cn | Beta | Streams | Claude Code |
| Anthropic Messages | POST | https://www.deepcodex.cn | Beta | No stream | Claude Code |
| OpenAI Completions (FIM) | POST | https://www.deepcodex.cn | Supported | Streams | Completion / FIM |
| Model list | GET | https://www.deepcodex.cn | Supported | No stream | Model discovery by terminals |
Protocol support
| Plan | OpenAI Chat | Anthropic Messages | OpenAI Responses |
|---|---|---|---|
| 免费版 | Supported | Not included | Not included |
| 入门版 | Supported | Not included | Not included |
| 专业版 | Supported | Not included | Not included |
| Pro Plus | Supported | Supported | Supported |
| Pro Max | Supported | Supported | Supported |
Authentication
Base URL
https://www.deepcodex.cn/v1- 01OpenAI-compatible routes use Authorization: Bearer <your key>. Subsequent requests are rejected after a key is revoked.
- 02The Anthropic Messages bridge reads the x-api-key header, or ANTHROPIC_AUTH_TOKEN when set for Claude Code.
- 03Explicit project selection takes precedence: for OpenAI Chat / Responses, metadata.project_id overrides X-DeepCodex-Project. Anthropic Messages uses the header; metadata.user_id is client identity and is not used for project attribution. Otherwise, Codex CLI and Claude Code identify projects by installation identity and exact working directory. A generic SDK without this context remains unattributed and does not inject project memory; there is no automatic fallback to the key binding or account default. Use an owned project UUID or external_ref (created on first use); trim to 1–128 UTF-8 bytes without control characters.
Authorization: Bearer <your-api-key>
X-DeepCodex-Project: <project-uuid-or-external-ref>
Content-Type: application/jsonModels
| Model | Context | Max output | Best for | Notes | Ratio |
|---|---|---|---|---|---|
deepseek-v4.1-flashDeepSeek V4.1 Flash | 1M | 393.2K | Quick rewrites, retrieval, batches of small tasks | Vision · Thinking | 1× |
Examples
curl --no-buffer https://www.deepcodex.cn/v1/chat/completions \
-H "Authorization: Bearer $DEEPCODEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"deepseek-v4.1-flash","stream":true,"messages":[{"role":"user","content":"Make the order callback idempotent"}]}'Terminal setup
Minimum plan: Pro Plus (protocol and API-key model permissions still apply)
Config location: ~/.codex/config.toml
# ~/.codex/config.toml
model = "deepseek-v4.1-flash"
model_provider = "deepcodex"
web_search = "disabled"
[model_providers.deepcodex]
name = "deepcodex"
base_url = "https://www.deepcodex.cn/v1"
env_key = "DEEPCODEX_API_KEY"
wire_api = "responses"export DEEPCODEX_API_KEY=dck_xxx
codexSend a short message to check the connection. For a 403 response, check that your plan includes the required protocol. Keep keys out of source control.
Rate limits and quota
| Dimension | Meaning | Notes |
|---|---|---|
| Concurrency | Requests in flight at once | Per plan; burst concurrency is counted separately |
| RPM | Requests per minute | Can be tightened per key |
| QPS | Requests per second | Per account; plans without a ceiling take the platform default, and each key can tighten it |
| Period cap | Equivalent tokens per billing period | An in-app / email alert fires at 80% |
Response headers
Retry-After- Seconds to back off before retrying a rate-limited call
X-DeepCodex-RPM-Limit / -Remaining / -Reset- Ceiling, requests left and seconds to reset for the current minute
X-DeepCodex-QPS-Limit / -Remaining / -Reset- Ceiling, requests left and seconds to reset for the current second
X-DeepCodex-Quota-Remaining- Equivalent tokens left in this period
X-DeepCodex-Quota-Reset- Period quota reset timestamp in RFC3339 format, not a delay in seconds
Matching reason_code
concurrency_exceeded- Key or account concurrency limit exceeded; platform/model limits use platform_concurrency_exceeded
rpm_exceeded- Above the per-minute request limit
quota_exceeded- Period equivalent-token quota used up
qps_exceeded- Above the per-second request limit; retry after 1s
key_qps_exceeded- Above this API key's own per-second limit
Error codes
| Category | reason_code | HTTP | Meaning | What to do |
|---|---|---|---|---|
| Authentication | invalid_api_key | 401 | The key is unknown or malformed | Regenerate the key on the API Keys page and update the terminal config |
| Authentication | api_key_revoked | 401 | The key was revoked | The key was revoked; create a new one on the API Keys page |
| Authentication | api_key_expired | 401 | The key has passed its expiry date | The key passed its expiry; create a new one on the API Keys page |
| Authentication | key_disabled | 401 | The key is disabled; enable it on the keys page | Re-enable the key on the API Keys page or use another key |
| Authentication | user_disabled | 403 | The account is disabled | The account is disabled; contact support |
| Authentication | account_closed | 403 | The account was closed while the request was running | The account was closed during this request. Its keys can no longer be used; contact support if the closure was unexpected. |
| Authentication | ip_not_allowed | 403 | The caller's IP is outside the key's allow-list | Add the caller IP to the key's IP allowlist |
| Authentication | identity_required | 403 | Identity verification is required before calling | Finish identity verification in Settings before calling the API |
| Plan | no_active_subscription | 403 | No active plan | Subscribe to a plan on the plan page |
| Plan | subscription_expired | 403 | The plan period has ended | Renew the plan on the plan page |
| Plan | subscription_not_started | 403 | The plan period has not started | Wait for the plan period to begin or choose an active plan |
| Plan | quote_required | 422 | Plan | Review GET /api/me/subscription/change-plan/preview and submit its quote_token with a stable request_id. A new unreviewed operation is never charged. |
| Plan | invalid_quote | 422 | Plan | Obtain a new quote for this signed-in account. Do not edit the encrypted quote_token. |
| Plan | quote_expired | 409 | Plan | Review a fresh quote. If payment may already have completed, query the original order before paying again. |
| Plan | quote_conflict | 409 | Plan | Submit the exact reviewed quote without conflicting plan, period or action fields. |
| Plan | quote_already_used | 409 | Plan | Query the original purchase; the same quote cannot fund another entitlement change. |
| Plan | refresh_due | 409 | Plan | Your existing paid service has a new allowance period due. Admission and the renewal worker refresh it without selling another period. |
| Plan | contract_unverified | 409 | Plan | The old paid contract cannot be verified for a prorated charge. Keep existing rights and review a new purchase after paid service ends. |
| Plan | incompatible_upgrade | 409 | Plan | Schedule this change after all existing paid service ends; it cannot be priced as an in-period upgrade. |
| Plan | prepaid_periods_remaining | 409 | Plan | Keep the future periods already paid for. Use an eligible allowance pack, wait for the next refresh, or schedule a later change. |
| Plan | base_not_exhausted | 409 | Plan | A complete new period requires exhausted base allowance and no future prepaid periods. Key limits and reservations do not prove exhaustion. |
| Plan | no_pending_action | 409 | Plan | There is no future purchase arrangement to cancel. Reload the current subscription state. |
| Plan | client_not_in_plan | 403 | The plan does not include this terminal | Use a terminal included in the plan or upgrade |
| Plan | model_not_allowed_by_key | 403 | The key's model allow-list excludes this model | Relax the model restriction in the key detail |
| Plan | model_not_allowed_by_plan | 403 | The plan does not include this model | Pick a model included in the plan or upgrade |
| Plan | thinking_not_in_plan | 403 | The plan does not include Playground thinking mode | Turn off Playground thinking mode or use a plan that includes it |
| Plan | protocol_not_in_plan | 403 | The plan does not include this protocol | Use a protocol included in the plan or upgrade |
| Plan | capability_unavailable | 403 | This request needs image or hosted-tool capability the plan cannot fund | This request carries an image or a provider-executed tool; upgrade to a plan with an assist allowance, or send it without the image or tool |
| Request | model_required | 400 | The request has no model | Set the model field in the request body |
| Request | model_not_found | 404 | Unknown model code | Use a model code from GET /v1/models |
| Request | unsupported_model_protocol | 400 | This model cannot be called through this protocol | Call the model through the protocol listed in the model catalog |
| Request | delivery_approval_not_supported | 422 | Publishing runs do not support pre-delivery approval | Use a read-only workflow for manual review, or explicitly disable pre-delivery approval before publishing |
| Request | invalid_request_error | 400 | The request body failed validation | Fix the request body as described in the error message |
| Request | validation_failed | 422 | Console request parameters failed validation | Correct the field identified in the response message and retry. Invalid search parameters and invalid memory kinds return HTTP 422 with code validation_failed; search keywords must be valid UTF-8 without U+0000 and at most 200 characters. |
| Request | unsupported_parameter | 400 | The request contains unsupported stateful Responses parameters | Remove unsupported stateful Responses parameters and use a stateless request with store=false |
| Request | invalid_json | 400 | The body is not valid JSON | Send a valid JSON body with Content-Type: application/json |
| Request | body_unreadable | 400 | The body could not be read | Resend the request; check the client's connection and proxy |
| Request | body_too_large | 413 | The body exceeds the size limit | Shorten the prompt or split the request |
| Request | client_aborted | 499 | The caller disconnected before the request completed | The caller disconnected before completion. 499 is the request record status, not a response delivered to the disconnected client; inspect recorded usage before retrying |
| Request | invalid_project_id | 400 | The project reference format is invalid | Use an owned project UUID or an external_ref; after trimming, use 1–128 UTF-8 bytes without control characters. For OpenAI Chat/Responses, metadata.project_id takes precedence over the project header; Anthropic Messages uses X-DeepCodex-Project |
| Request | invalid_repository_branch | 400 | Request | Send optional branch context of at most 255 bytes without NUL, CR or LF. Use a string for metadata.repository_branch. X-DeepCodex-Branch is trimmed; metadata whitespace is retained. When a non-empty trimmed header is present, any included metadata value must match it exactly. Omit empty branch context. |
| Request | project_limit_reached | 400 | Too many projects on this account | Delete an unused project or upgrade the plan |
| Quota | quota_exceeded | 429 | Equivalent-token quota for the period is used up; the response carries the reset time | Top up, upgrade the plan or wait for the period reset named by X-DeepCodex-Quota-Reset. A Codex thread goal shows this as usage_limited and stops continuing; resume it with /goal resume once the allowance is back |
| Quota | key_quota_exceeded | 429 | This key's token cap for the period is used up | Raise the key's quota in the key detail or use another key. A Codex thread goal shows this as usage_limited; resume it with /goal resume after raising the cap |
| Quota | provider_quota_exceeded | 403 / 429 | The plan lacks this native provider allowance (403), or its allowance is exhausted (429) | For 403, choose a plan with this provider allowance or another included model. For 429, wait for the allowance to reset or switch model; resume a usage_limited goal with /goal resume once allowance is available |
| Platform | provider_capacity_exhausted | 429 | Shared model execution capacity is temporarily busy; retry shortly. This is independent of token allowance. | Shared provider capacity is busy, independently of your token allowance. Retry after Retry-After; no paid model call was dispatched for this refusal. |
| Quota | assist_quota_exceeded | 429 | The assistant's share of the allowance is used up | Wait for the assistant quota to reset or upgrade. A Codex thread goal shows this as usage_limited; resume it with /goal resume once the quota is back |
| Quota | run_budget_exceeded | 429 | The run family has exhausted its token budget | This run family exhausted its token budget; inspect the run budget before starting another run |
| Request | run_finished | 409 | This run has finished and its run key no longer accepts requests | This run has finished; use the key belonging to a new active run |
| Quota | rpm_exceeded | 429 | Above the per-minute request limit | Back off and retry per Retry-After; X-DeepCodex-RPM-Limit/-Remaining/-Reset show the window |
| Quota | key_rpm_exceeded | 429 | This API key's own per-minute ceiling is full | This API key has its own per-minute ceiling; raise it in the key detail or spread the load over more keys |
| Quota | qps_exceeded | 429 | Above the per-second request limit | Spread requests out and retry per Retry-After (1s); X-DeepCodex-QPS-Limit/-Remaining/-Reset show the window |
| Quota | key_qps_exceeded | 429 | This API key's own per-second ceiling is full | This API key has its own per-second ceiling; raise it in the key detail or spread the load over more keys |
| Quota | concurrency_exceeded | 429 | Above the key, account or platform concurrency limit | Lower concurrency or upgrade; retry per Retry-After (2s). A slot frees when an in-flight call finishes, so there is no window to wait out |
| Quota | platform_concurrency_exceeded | 429 | The platform is at capacity for this model; retry shortly | Retry with backoff; the platform is at capacity |
| Upstream | upstream_rate_limited | 429 | The upstream provider throttled the call; retry shortly | Retry with backoff; the gateway does not replay this failed request with another credential. A new attempt is a separate request |
| Upstream | upstream_rejected_request | 400 / 404–428 / 430–499 | The upstream provider rejected the request; its message is passed through | The record uses upstream_rejected_request; the upstream response body and 4xx status are passed through (for example 400, 404 or 422). Inspect that body to correct the request |
| Upstream | upstream_error | 502 | The upstream provider returned an error | Retry; the reason is in the request detail |
| Upstream | upstream_auth_failed | 502 | The platform credential was refused upstream | The request record uses upstream_auth_failed; the response error.code is upstream_error. Retry with backoff and contact support if it persists |
| Upstream | upstream_unreachable | 502 | The upstream provider could not be reached | Check the request record, then retry with backoff. The gateway does not replay this failed request with another credential |
| Upstream | upstream_timeout | 504 | The upstream provider did not answer in time | Shorten the output or retry |
| Upstream | upstream_stream_broken | 502 | The stream ended early | Check request details and recorded usage before retrying; a new request incurs separate usage |
| Upstream | upstream_response_invalid | 502 | The upstream answer could not be parsed | Retry; the reason is in the request detail |
| Upstream | upstream_request_invalid | 502 | The translated upstream request could not be built | The translated upstream request could not be built. The record uses upstream_request_invalid; the response error.code is upstream_error. Contact support if it persists |
| Platform | no_upstream_available | 503 | No healthy upstream for this model right now | Retry shortly or pick another model |
| Platform | auth_unavailable | 503 | Key lookup is temporarily unavailable | Retry shortly; contact support if it persists |
| Platform | identity_unavailable | 503 | Identity lookup is temporarily unavailable | Retry shortly; contact support if it persists |
| Platform | admission_unavailable | 503 | Quota accounting is temporarily unavailable | Retry shortly; contact support if it persists |
| Platform | model_catalog_unavailable | 503 | The model catalog is temporarily unavailable | Retry shortly; contact support if it persists |
| Platform | upstream_catalog_unavailable | 503 | The upstream catalog is temporarily unavailable | Retry shortly; contact support if it persists |
| Request | image_invalid | 400 | The image data or format is invalid | Check the prompt length, size and image format |
| Request | image_prompt_invalid | 400 | The image description or question is invalid (empty or invisible generation description, over 1800 characters, or a null character U+0000) | Provide a generation description with visible text, keep descriptions and questions within 1800 characters, and remove null characters (U+0000). Recognition questions may be left blank. |
| Request | image_size_unavailable | 400 | The requested image size does not match the current service configuration | Refresh the image workbench size settings, then retry with a currently supported size. |
| Upstream | image_refused | 422 | The image model explicitly declined this recognition request | Change the question or image before trying again; waiting for service recovery will not change this refusal. |
| Request | image_too_large | 413 | The image exceeds the size limit | Upload a smaller image (see image_max_bytes) |
| Plan | images_not_in_plan | 403 | The plan does not include the image workbench | Upgrade to a plan that includes the image workbench |
| Platform | images_unconfigured | 503 | Image generation has not been configured | Image generation is not configured yet; contact support |
| Upstream | upstream_failed | 502 | The upstream request failed | Retry; the reason is in the request detail |
| Platform | not_supported | 501 | The current client version does not support this operation | Update the assistant to a newer version |
| Platform | assist_disabled | 503 | The assistant feature is currently disabled | Retry shortly; contact support if it persists |
| Platform | memory_query_failed | 503 | The project memory query failed | Retry shortly; contact support if it persists |
| Request | invalid_memory_kind | 422 | The memory kind is invalid | Use one of fact, pitfall, decision, preference or structure. This log reason is returned by console APIs as HTTP 422 with code validation_failed. |
| Request | saved_search_empty | 400 | A saved search cannot be empty | Enter a keyword before saving the search condition. Empty conditions return HTTP 400 with code saved_search_empty. |
| Request | saved_search_limit | 409 | The saved-search limit has been reached | Delete a saved condition before adding another |
| Request | saved_search_duplicate | 409 | This search condition has already been saved | This condition is already saved; open it from the list |
Webhooks
Where to configure
Console → Settings → Webhooks: enter a public https endpoint, pick the events, and copy the signing secret — it is shown once. Up to 10 endpoints per account; each can be disabled, have its secret rotated, or be sent a test event. Delivery records are kept for 30 days.
Webhook access depends on the capability enabled in the current subscription, not a fixed plan name; without that capability the settings tab shows an upgrade notice.
Request body
Every delivery is a POST with Content-Type application/json. The envelope is always id / type / occurred_at / data; the shape of data depends on the event type.
{
"id": "agent_run.completed:0f2c0000-0000-4000-8000-000000000001:done",
"type": "agent_run.completed",
"occurred_at": "2026-09-17T08:30:11Z",
"data": { "run_id": "0f2c0000-0000-4000-8000-000000000001", "status": "done", "tokens": 18422 }
}| Event | Fires when | Main data fields |
|---|---|---|
agent_run.completed | A cloud agent run finishes: done (success), failed, or canceled | run_id, status, summary, tokens, cost_cny_cents, compute_seconds, engine, caller, repo, branch, mode, created_at, finished_at; plus project_id, task_id, tests_passed when known |
task.summarized | A conversation is digested into a task summary and cards | task_id, project_id, summary, intent_level, request_count, equiv_tokens, cards[], started_at, ended_at |
workflow_run.completed | A workflow run finishes | workflow_run_id, workflow_id, workflow_name, status, repo, branch, steps_completed, tokens, cost_cny_cents, created_at, finished_at |
usage_threshold | This period's usage crosses an alert threshold | title, message, notified_at, threshold_percent, usage_percent, used_equiv_tokens, total_equiv_tokens, period_end, plan_code |
goal.completed | A goal completes | goal_id, task_id, status, objective, rounds_done, tokens_used, time_used_seconds, rounds |
goal.blocked | A goal stops because it is blocked | goal_id, task_id, status, objective, rounds_done, tokens_used, time_used_seconds, blocker, blocked_streak |
The list above contains subscribable events; unknown subscription types are rejected, and selecting none subscribes to all of them. A manually sent test event uses webhook.test with endpoint_id and message in data; webhook.test is not a subscription type. Payloads are assembled from an explicit field allow-list.
Verifying the signature
Request headers
X-Deepcodex-Event- Event type, the same value as the body's type
X-Deepcodex-Event-Id- Event id; unique per endpoint — use it for idempotency
X-Deepcodex-Delivery- Delivery record id; unchanged across retries
X-Deepcodex-Attempt- Attempt number, starting at 1
X-Deepcodex-Timestamp- Signing timestamp (Unix seconds)
X-Deepcodex-Signature- Formatted as t=<timestamp>,v1=<hex>
Node.js example
import crypto from "node:crypto";
// X-Deepcodex-Signature: t=<unix seconds>,v1=<hex hmac>
export function verifyDeepcodexWebhook(rawBody, signature, secret) {
if (typeof signature !== "string") return false;
const parts = /^t=(\d+),v1=([a-fA-F0-9]{64})$/.exec(signature);
if (!parts) return false;
const [, timestamp, digest] = parts;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) >= 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const given = Buffer.from(digest, "hex");
const want = Buffer.from(expected, "hex");
return given.length === want.length && crypto.timingSafeEqual(given, want);
}Delivery and retries
| Rule | Detail |
|---|---|
| Success | Any 2xx counts as delivered. The request times out after 10 seconds and 3xx redirects are not followed, so a signed payload never reaches an address you did not register |
| Backoff | At most 5 attempts including the initial delivery. The first 4 failures wait 30 seconds, 2 minutes, 10 minutes and 1 hour; the fifth failure stops delivery. Total backoff is 1 hour 12 minutes 30 seconds, plus request time and scheduling delays. |
| At least once | Delivery is at-least-once: key your handler on X-Deepcodex-Event-Id and ignore an id you have already processed |
| When it stops | Disabling or deleting an endpoint voids its queued deliveries; an event that fails five times is not retried again, and the delivery record keeps the last response code and error |
| Endpoint rules | A public https URL, without credentials in it, and never a loopback, private or link-local address |