DeepSeek Harness setup: API key, Base URL, and models
Configure DeepSeek Harness (dsh) with a BetterToken API Key, Base URL, and Model ID through a headless profile. Choose Chat Completions, Responses, or Anthropic Messages.
Connect DeepSeek Harness (dsh) to BetterToken with a custom provider in the headless profile. In macOS testing, @deepseek-ai/dsh@0.2.1-rc.2 completed text requests and file-reading tool calls 3 out of 3 times with each of these combinations: Chat Completions with a GPT model, Responses with a GPT model, Anthropic Messages with a Claude model, and Anthropic Messages with a GPT model.
Key settings
| Field | Value |
|---|---|
| API Key | BetterToken API Key, read from the environment variable named by apiKeyEnv |
| Base URL | https://bettertoken.ai/v1 for Chat Completions and Responses |
| Base URL for Anthropic Messages | https://bettertoken.ai, without /v1 |
| Model | YOUR_MODEL_ID |
| Config file | $DSH_HOME/profiles/headless/cordis.patch.yml (DSH_HOME defaults to ~/.dsh) |
Prerequisites
- Node.js 24.9 or later, as required by the official rc.2 upgrade notes
- A BetterToken API Key: register and get one
- A Model ID copied from the model plaza
Install
Run the version tested here without a global install:
npx @deepseek-ai/dsh@0.2.1-rc.2 --help0.2.1-rc.2 was the latest npm tag during testing, and it is a release candidate. Other tags, such as next, were not tested.
Manual setup
Export the API Key, replacing YOUR_API_KEY:
export BETTERTOKEN_API_KEY="YOUR_API_KEY"Create ~/.dsh/profiles/headless/cordis.patch.yml. The file is a list of Cordis patches. Replace both YOUR_MODEL_ID values with the same exact Model ID:
Chat Completions
- id: llm-pi-ai config: providers: bettertoken: api: openai-completions baseURL: https://bettertoken.ai/v1 apiKeyEnv: BETTERTOKEN_API_KEY retryPolicy: mode: normal maxRetries: 2 models: - id: YOUR_MODEL_ID- id: agent-default-model config: provider: bettertoken model: YOUR_MODEL_IDResponses
- id: llm-pi-ai config: providers: bettertoken: api: openai-responses baseURL: https://bettertoken.ai/v1 apiKeyEnv: BETTERTOKEN_API_KEY retryPolicy: mode: normal maxRetries: 2 models: - id: YOUR_MODEL_ID- id: agent-default-model config: provider: bettertoken model: YOUR_MODEL_IDAnthropic Messages
- id: llm-pi-ai config: providers: bettertoken: api: anthropic-messages baseURL: https://bettertoken.ai apiKeyEnv: BETTERTOKEN_API_KEY retryPolicy: mode: normal maxRetries: 2 models: - id: YOUR_MODEL_ID- id: agent-default-model config: provider: bettertoken model: YOUR_MODEL_IDapiKeyEnv holds the name of an environment variable, not the API Key and not a ${...} expression. A custom provider needs api, baseURL, and at least one entry under models; only the model id is required in each entry. If llm-pi-ai is already configured, copy the providers you want to keep into the same config, because a new entry replaces the existing one.
The api field accepts only openai-completions, openai-responses, and anthropic-messages for custom providers. The Anthropic Base URL is the site root because the client adds /v1/messages itself.
To check the syntax without sending a model request, run npx @deepseek-ai/dsh@0.2.1-rc.2 --profile headless --dump-config.
Verify the connection
Run this in the same terminal:
npx @deepseek-ai/dsh@0.2.1-rc.2 --profile headless --json "Reply with only: OK"--json prints one JSON event per line. Check that the exit code is 0, that turn_end.reason shows a normal ending, and that the last final event has text equal to OK. A failed run can still print an empty final event, so do not rely on the event alone.
To check file access, create sample.txt in an empty directory, run the same command from that directory with the prompt Read sample.txt using a tool. Reply with only its exact contents., and confirm that the output contains a tool_call, a tool_result with the file text, and a matching final text.
Switch models
Change both YOUR_MODEL_ID values to another exact Model ID. If the new model uses a different protocol, such as a Claude model through Anthropic Messages, change api and baseURL together. To list the models the gateway returns, use Fetch available models in the DeepSeek Harness Web UI with a custom provider. During testing the same discovery service returned the BetterToken model list for all three protocols.
Common errors
| Symptom | What to check |
|---|---|
MISSING_CREDENTIAL, exit code 1, and no request in the Dashboard | The process did not see the variable named by apiKeyEnv. Export it in the terminal that starts dsh, and check the spelling. This check runs locally. |
A Key saved in the Web UI is ignored by the headless profile | The headless profile reads credentials only from the launch environment, then .env in the current directory, then .env under DSH_HOME, in that order. |
404 with a request path ending /v1/v1/messages | The Anthropic baseURL ends with /v1. Use https://bettertoken.ai. |
| Model not found | Copy the Model ID again from the model plaza and use it in both places. |
| Many retries after a failure | If retryPolicy is omitted, the adapter defaults to 5 retries. Set maxRetries explicitly, as in the examples. |
| Node.js version error | Use Node.js 24.9 or later. |
Related
Technical details
Actual request paths in testing were /v1/chat/completions, /v1/responses, and /v1/messages for the three api values. dsh also sends an automatic session-title request through the configured provider and model, so one task can produce more than one request. Default output limits were added by the client, for example 32768 tokens on the main request, and were not set in the configuration.
The input and contextWindow fields in a model entry are declarations to the client. They do not query or prove the gateway’s limits, and image input was not tested. Only macOS was tested.

