For agents
Built to be driven by other programs. The contract below is stable and tested; the same text ships in the repo as AGENTS.md and as a drop-in agent skill.
The output contract
- stdout is data. The answer text, or with
--jsonthe API's own response body. - stderr is status. Chain of thought, the usage line, truncation warnings, verbose HTTP, errors.
ds chat "..." > answer.txtwrites the answer and nothing else.
--json is not wrapped
--json prints the API's response byte-for-byte: no envelope, no
injected fields. jq recipes written against the OpenAI or Anthropic APIs keep
working unchanged.
ds chat "..." --json | jq -r '.choices[0].message.content'
ds chat "..." --jq '.usage'
ds models --json | jq -r '.data[].id'
Cost deliberately does not appear there — it would have meant wrapping the response. It goes to stderr and to the ledger instead.
Response shapes
From the API, so they are the documented DeepSeek shapes:
chat {"id","object","created","model",
"choices":[{"message":{"content","reasoning_content","tool_calls"},
"finish_reason"}],"usage":{...}}
anthropic {"id","type","role","model","content":[{"type","text"}],
"stop_reason","usage":{...}}
respond {"id","object","status","model",
"output":[{"type","content":[{"type","text"}]}],"usage":{...}}
fim {"id","object","model","choices":[{"text","finish_reason"}],"usage":{...}}
models {"object":"list","data":[{"id","object","owned_by"}]}
balance {"is_available","balance_infos":[{"currency","total_balance",...}]}
Computed locally, not from the API:
usage {"since","total":{...},"by_model":{...},"by_api":{...}}
check {"base_url","key_set","ok",
"probes":[{"name","path","ok","detail","error","ms"}]}
session ls [{"name","model","turns","updated","bytes"}]
Note that usage field names differ per format, exactly as the API sends them. See the token-accounting trap.
Exit codes carry meaning
0 success
1 error read stderr
2 auth key missing or rejected → do not retry
3 no balance top up → do not retry
4 rate limited back off, then retry
130 interrupted
Transport failures and 429/5xx are already retried internally with
exponential backoff, honouring Retry-After. A non-zero exit means
your own retry loop probably will not help either. Requests that reached the
model are never retried — a second call would be billed twice.
Preflight
ds check --json
# {"base_url":"...","key_set":true,"probes":[...],"ok":true}
One command, six endpoints, a fraction of a cent. If ok is
false, read probes[].error.
Errors say what to do
$ ds chat hi
Error: Insufficient Balance (HTTP 402)
out of balance: deepseek balance — top up at https://platform.deepseek.com/top_up
Quiet mode
ds chat "..." --no-stats --no-ledger --json
Use both when the usage line would pollute captured output, or when a batch run should not land in the ledger.
As an agent skill
skill/SKILL.md ships in every release archive. It carries the same contract as an operational procedure, with the rules that matter for autonomous use:
- Report costs as estimates, never as billed amounts.
- Do not retry on exit 2 or 3 — bring them to the human.
- The API is text-only: no images, no documents.
- Slow starts of up to ten minutes are normal under load, not a failure.