[Dagon Inference](https://inference.dagon.so/) / Use Dagon from your boat box

# Use Dagon from your boat box

Published 2026-11-11 · Last updated 2026-11-11

Store a Dagon Inference key as a secret variable in a [boat environment](https://docs.boat.dev/environments), set `DAGON_BASE_URL=https://inference.dagon.so/v1` next to it, and give the harnesses in the sandbox a custom OpenAI-compatible provider: [one setup script](https://inference.dagon.so/boat/dagon-setup.sh) does it for pi, Prime Agent, OpenCode, Mistral Vibe, Claude Code and Codex, and Kimi Code takes four variables. boat bills the sandbox's time; Dagon bills the tokens, from your prepaid USDC balance.

## How do I create a Dagon key for a boat sandbox?

Create a key in the [console](https://inference.dagon.so/app/) or with `curl -X POST https://inference.dagon.so/v1/keys` (no email or password), and fund it with USDC from $5. Every model and price is on [the pricing page](https://inference.dagon.so/abliterated-llm-pricing).

Give the sandboxes a [child key](https://inference.dagon.so/docs/child-keys), not the key that holds your balance: a child has its own balance and an optional expiry, so a sandbox can spend at most what you put on it.

```
# On your own computer, with the root key (never put the root key in a sandbox)
curl https://inference.dagon.so/v1/keys/children \
  -H "Authorization: Bearer $DAGON_ROOT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "boat: dagon environment", "amount_usd": 5, "expires_at": "2027-01-01T00:00:00Z"}'
# The reply's "key" is shown once: put it in DAGON_KEY for the next step
```

Same plug, new socket

The gateway answers on four paths: /v1/chat/completions and /v1/responses for OpenAI SDKs, /v1/messages for Anthropic SDKs, and /v1/messages/count\_tokens, a free pass-through. Your client code stays as it is: point its base URL at the gateway and give it a gateway key.

## How do I store the key as a boat secret?

As an environment variable of a boat environment. boat puts the environment's variables into every shell of every new sandbox that uses it ([Secrets](https://docs.boat.dev/environments/secrets)), so the key is never in a repository, a prompt or a saved snapshot. boat's docs say not to put secrets in prompts, URLs, command-line arguments that can reach logs, Docker build args or committed files, and to keep them in an environment rather than a saved snapshot, which keeps every file on its disk ([Which one to use](https://docs.boat.dev/environments/choose)). `boat env set-var` reads the value from stdin when you leave it off the command line ([CLI reference](https://docs.boat.dev/cli-reference)).

```
# One environment for the sandboxes that use Dagon (or use your default one, "base")
boat env new dagon

# The key, read from stdin so it stays out of your shell history and process list
printf %s "$DAGON_KEY" | boat env set-var dagon DAGON_API_KEY
boat env set-var dagon DAGON_BASE_URL=https://inference.dagon.so/v1

# Check: Secrets on, safe for third parties off, both variables listed
boat env info dagon
```

- An environment marked **safe for third parties**, a sandbox made with `--no-env`, or an environment with its **Secrets** toggle off passes no variables at all ([Access and credentials](https://docs.boat.dev/environments/access)).
- A running sandbox keeps the environment version it started with. After a change, run `boat env upgrade dagon` ([Versions and upgrades](https://docs.boat.dev/environments/versions)).
- For one sandbox only, `boat new --env DAGON_API_KEY=…` sets a variable on that sandbox, but puts the key on your command line.

## What does every sandbox get?

Two variables, the same in every sandbox of the environment:

| Variable | Value |
| --- | --- |
| `DAGON_API_KEY` | Your `gk_` key (secret) |
| `DAGON_BASE_URL` | `https://inference.dagon.so/v1`: the OpenAI-compatible base URL. Anthropic SDKs use `https://inference.dagon.so`, without `/v1`. |

boat has no file format to import an environment from: `POST /environments` takes only a name, and each variable is one more call ([Manage environments](https://docs.boat.dev/environments/manage)). The commands above are the template. For the harness configs, boat runs a script you pass at create with `--setup-file`, once, as the sandbox's user ([Setup scripts](https://docs.boat.dev/setup)):

```
curl -fsSLO https://inference.dagon.so/boat/dagon-setup.sh
boat new --environment dagon --setup-file dagon-setup.sh
boat info <id>     # wait for setupStatus: done
```

## Which harnesses can use Dagon?

Every boat sandbox comes with seven coding-agent harnesses ([Harnesses and models](https://docs.boat.dev/integrated-agents/harnesses-and-models)). We ran each one against a capture of its first request and checked that request with the gateway's own validation, on the versions named here (2026-11-11); Claude Code and Codex also completed a turn with a tool call through the gateway, with a stand-in model behind it. boat installs the latest release of each, so later versions can differ.

| Harness | Works with Dagon | How |
| --- | --- | --- |
| pi (1.2.0) | Yes | Custom provider in `~/.pi/agent/models.json`. Its Responses and Messages modes were accepted too. |
| Prime Agent (0.9.4) | Yes, on Chat Completions | Same file format in `~/.prime/agent/models.json`. Its Messages mode sends `anthropic-beta: interleaved-thinking-2025-05-14`, which the gateway now accepts; we have not re-run that mode. |
| OpenCode (1.19.5) | Yes | Custom provider in `~/.config/opencode/opencode.json`. |
| Kimi Code (2.5.3) | Yes | The `KIMI_MODEL_*` variables, no file. |
| Mistral Vibe (3.1.2) | Yes, on Chat Completions | Provider in `~/.vibe/config.toml`. Its Anthropic style sends `anthropic-beta` values and was refused in our test; we have not re-run it since the gateway began accepting interleaved thinking. |
| Claude Code (2.1.310) | Yes, on Messages | `~/.claude/settings.json`: the base URL, a key helper that prints `$DAGON_API_KEY` and the model. The gateway accepts its `anthropic-beta` values and `context_management` and forwards neither. |
| Codex (0.164.0) | Yes, on Responses | A profile, `~/.codex/dagon.config.toml`, run with `codex --profile dagon`. Codex speaks only Responses to a custom provider; the gateway drops its `client_metadata`, its `namespace` tool groups (multi-agent and MCP tools are not offered) and a listed `web_search` tool. |

The sandboxes also have Hermes Agent, OpenClaw and the Cursor CLI agent ([Pre-installed tools](https://docs.boat.dev/machines/tools)); we have not tested them. We have not verified `boat prompt` with a custom provider: run the harness yourself with `boat ssh` or `boat exec`, which boat documents for every built-in harness ([Customize the harness](https://docs.boat.dev/integrated-agents/customize)).

## Which Codex capabilities are unavailable?

Hosted web search and namespace tools (including multi-agent and MCP groups) are unavailable. Optional tools are omitted and reported in the `x-dagon-dropped-tools` response header, for example `web_search,namespace:multi_agent_v1`. If nothing remains, the gateway removes both the tools and an optional `tool_choice`. Forced choices naming omitted tools, namespace choices, `allowed_tools`, and `required`/`any` with no callable tools are refused before upstream work. The setup profile sets `web_search = "disabled"` and `[features] multi_agent = false` ([Codex configuration reference](https://developers.openai.com/codex/config-reference)); existing MCP configuration is untouched.

## How do I set up the harnesses?

Run the setup script once per sandbox, with `--setup-file` as above or with `bash dagon-setup.sh` in a running sandbox. It reads the model list from `GET /v1/models`, adds a provider named `dagon` to the pi, Prime Agent, OpenCode and Mistral Vibe configs with every model, points Claude Code at the gateway and adds a `dagon` Codex profile, keeps whatever else the files hold (boat registers its own `computer` MCP server in some of them), and writes no key: each config names the `DAGON_API_KEY` variable. Running it again changes nothing. It is served at [`https://inference.dagon.so/boat/dagon-setup.sh`](https://inference.dagon.so/boat/dagon-setup.sh):

```
#!/usr/bin/env bash
# Dagon Inference in a boat sandbox: adds Dagon as a custom provider to pi, Prime Agent, OpenCode, Mistral Vibe, Claude
# Code and Codex, with every model GET /v1/models lists. Kimi Code needs no file: it takes the KIMI_MODEL_* variables.
#
#   boat new --setup-file dagon-setup.sh          # once, at create (runs as the user "user")
#   bash dagon-setup.sh                           # or by hand in a running sandbox; running it again changes nothing
#
# The key is never written to disk: each config names the DAGON_API_KEY variable, which the boat environment supplies.
# DAGON_BASE_URL, if set, replaces the default base URL. Existing configs are kept, and a provider named "dagon" that
# is already there is left as it is.
set -euo pipefail
python3 - <<'PY'
import json, os, urllib.request

base = os.environ.get("DAGON_BASE_URL", "https://inference.dagon.so/v1").rstrip("/")
home = os.path.expanduser("~")
with urllib.request.urlopen(f"{base}/models", timeout=30) as r:
    models = [m for m in json.load(r)["data"] if not m.get("deprecated")]
if not models:
    raise SystemExit(f"no models at {base}/models")

def path(*parts):
    p = os.path.join(home, *parts)
    os.makedirs(os.path.dirname(p), exist_ok=True)
    return p

def add_json(file, key, name, value):
    data = {}
    if os.path.exists(file):
        with open(file) as f:
            data = json.load(f)
    section = data.setdefault(key, {})
    if name in section:
        return print(f"{file}: {key}.{name} already set, left as it is")
    section[name] = value
    with open(file, "w") as f:
        json.dump(data, f, indent=2)
        f.write("\n")
    print(f"{file}: added {key}.{name}")

MARK = "# Dagon Inference provider (dagon-setup.sh)"

def add_toml(file, text):
    old = open(file).read() if os.path.exists(file) else ""
    tables = [l for l in text.splitlines() if l.startswith("[") and not l.startswith("[[")]
    if MARK in old or any(t in old.splitlines() for t in tables):
        return print(f"{file}: Dagon provider already set, left as it is")
    with open(file, "a") as f:
        f.write(("\n" if old and not old.endswith("\n") else "") + ("\n" if old else "") + MARK + "\n" + text)
    print(f"{file}: added the Dagon provider")

q = json.dumps  # a JSON string is a valid TOML basic string for these ids and URLs

# pi: ~/.pi/agent/models.json, the key read from $DAGON_API_KEY ("$NAME" interpolation).
pi_models = [{"id": m["id"], "name": m.get("name", m["id"]), "contextWindow": m["context_length"]} for m in models]
add_json(path(".pi", "agent", "models.json"), "providers", "dagon",
         {"baseUrl": base, "api": "openai-completions", "apiKey": "$DAGON_API_KEY", "models": pi_models})

# Prime Agent: the same file format, but apiKey names the variable without "$".
add_json(path(".prime", "agent", "models.json"), "providers", "dagon",
         {"baseUrl": base, "api": "openai-completions", "apiKey": "DAGON_API_KEY", "models": pi_models})

# OpenCode 1.x: a custom provider on the OpenAI-compatible AI SDK package; {env:NAME} reads the key.
add_json(path(".config", "opencode", "opencode.json"), "provider", "dagon",
         {"npm": "@ai-sdk/openai-compatible", "name": "Dagon Inference",
          "options": {"baseURL": base, "apiKey": "{env:DAGON_API_KEY}"},
          "models": {m["id"]: {"name": m.get("name", m["id"])} for m in models}})

# Mistral Vibe: an OpenAI-style provider and one model alias "dagon/<id>" per model. Pick one with VIBE_ACTIVE_MODEL.
add_toml(path(".vibe", "config.toml"),
         f'[[providers]]\nname = "dagon"\napi_base = {q(base)}\napi_key_env_var = "DAGON_API_KEY"\napi_style = "openai"\n'
         + "".join(f'\n[[models]]\nname = {q(m["id"])}\nprovider = "dagon"\nalias = {q("dagon/" + m["id"])}\n' for m in models))

def first(endpoint):
    return next((m for m in models if endpoint in m.get("endpoints", [])), models[0])

# Claude Code: ~/.claude/settings.json. apiKeyHelper prints $DAGON_API_KEY, so no key is written; the Anthropic base
# URL has no /v1. "[1m]" tells Claude Code a model's window is 1M tokens; it drops the suffix before sending.
claude = path(".claude", "settings.json")
settings = json.load(open(claude)) if os.path.exists(claude) else {}
if "apiKeyHelper" in settings or "ANTHROPIC_BASE_URL" in settings.get("env", {}):
    print(f"{claude}: a key helper or base URL already set, left as it is")
else:
    m = first("/v1/messages")
    cc_model = m["id"] + ("[1m]" if m["context_length"] >= 1_000_000 else "")
    settings["apiKeyHelper"] = 'printf %s "$DAGON_API_KEY"'
    settings.setdefault("env", {}).update({"ANTHROPIC_BASE_URL": base.removesuffix("/v1"),
                                           "ANTHROPIC_MODEL": cc_model, "ANTHROPIC_DEFAULT_HAIKU_MODEL": cc_model})
    with open(claude, "w") as f:
        json.dump(settings, f, indent=2)
        f.write("\n")
    print(f"{claude}: added the Dagon base URL, key helper and model {cc_model}")

# Codex: a profile file, ~/.codex/dagon.config.toml, layered over config.toml by codex --profile dagon. It holds a
# Responses provider (the only API Codex speaks to a custom provider); env_key names the variable. Web search is off:
# the gateway does not run it. features.multi_agent=false disables the collaboration namespace (Codex config reference).
add_toml(path(".codex", "dagon.config.toml"),
         f'model_provider = "dagon"\nmodel = {q(first("/v1/responses")["id"])}\nweb_search = "disabled"\n'
         f'\n[features]\nmulti_agent = false\n'
         f'\n[model_providers.dagon]\nname = "Dagon Inference"\nbase_url = {q(base)}\nenv_key = "DAGON_API_KEY"\nwire_api = "responses"\n')
PY
```

Then, in the sandbox, with GLM-5.3 Abliterated (`glm-5.3-abliterated`, served by Refuseless, Experiential Labs and Silk Compute, $1.10 input, $0.165 cached input and $2.20 output per 1M tokens) as the example model:

```
pi -p --model dagon/glm-5.3-abliterated "Summarize this repo"
prime-agent -p --model dagon/glm-5.3-abliterated "Summarize this repo"
opencode run -m dagon/glm-5.3-abliterated "Summarize this repo"
VIBE_ACTIVE_MODEL=dagon/glm-5.3-abliterated vibe -p "Summarize this repo"
KIMI_MODEL_NAME=glm-5.3-abliterated KIMI_MODEL_PROVIDER_TYPE=openai \
  KIMI_MODEL_BASE_URL="$DAGON_BASE_URL" KIMI_MODEL_API_KEY="$DAGON_API_KEY" \
  KIMI_MODEL_MAX_CONTEXT_SIZE=1048576 kimi -p "Summarize this repo"
claude -p "Summarize this repo"                     # model: ANTHROPIC_MODEL in ~/.claude/settings.json
codex exec --profile dagon "Summarize this repo"
```

## What does each harness config look like?

What the script writes, for one model, if you would rather edit the files yourself.

**pi**, `~/.pi/agent/models.json`. `"$DAGON_API_KEY"` reads the variable ([pi models configuration](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/models-configuration.md)):

```
{
  "providers": {
    "dagon": {
      "baseUrl": "https://inference.dagon.so/v1",
      "api": "openai-completions",
      "apiKey": "$DAGON_API_KEY",
      "models": [{ "id": "glm-5.3-abliterated", "contextWindow": 1048576 }]
    }
  }
}
```

**Prime Agent**, `~/.prime/agent/models.json`: the same, except `"apiKey": "DAGON_API_KEY"`, without the `$`. Prime Agent sent the `$DAGON_API_KEY` form as a literal key in our test.

**OpenCode**, `~/.config/opencode/opencode.json`, merged into what is there. `{env:NAME}` reads a variable. This is the schema of OpenCode 1.x, the current release; opencode.ai's docs already describe a newer `providers` schema, so check `opencode --version` first.

```
{
  "provider": {
    "dagon": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "Dagon Inference",
      "options": { "baseURL": "https://inference.dagon.so/v1", "apiKey": "{env:DAGON_API_KEY}" },
      "models": { "glm-5.3-abliterated": { "name": "GLM-5.3 Abliterated" } }
    }
  }
}
```

**Mistral Vibe**, appended to `~/.vibe/config.toml`. Pick the model with the `VIBE_ACTIVE_MODEL` variable ([Vibe configuration](https://docs.mistral.ai/vibe/code/cli/configuration)): `--model` alone still asks for the active model's key.

```
[[providers]]
name = "dagon"
api_base = "https://inference.dagon.so/v1"
api_key_env_var = "DAGON_API_KEY"
api_style = "openai"

[[models]]
name = "glm-5.3-abliterated"
provider = "dagon"
alias = "dagon/glm-5.3-abliterated"
```

**Kimi Code** builds a model from `KIMI_MODEL_NAME`, `KIMI_MODEL_PROVIDER_TYPE`, `KIMI_MODEL_BASE_URL` and `KIMI_MODEL_API_KEY`, without touching its config file ([Kimi Code environment variables](https://moonshotai.github.io/kimi-code/en/configuration/env-vars.html)), as in the commands above. A model alias in `config.toml` works only when `default_model` is set.

**Claude Code**, `~/.claude/settings.json`, left alone if it already sets a key helper or base URL. The base URL has no `/v1`; `apiKeyHelper` runs through the sandbox's shell and prints the variable, so no key is written ([Connect Claude Code to an LLM gateway](https://code.claude.com/docs/en/llm-gateway-connect)). `[1m]` after a model id tells Claude Code its window is 1M tokens; it drops the suffix before sending. Structured output (`output_config.format`) is refused, and Claude Code retries without it.

```
{
  "apiKeyHelper": "printf %s \"$DAGON_API_KEY\"",
  "env": {
    "ANTHROPIC_BASE_URL": "https://inference.dagon.so",
    "ANTHROPIC_MODEL": "glm-5.3-abliterated",
    "ANTHROPIC_DEFAULT_HAIKU_MODEL": "glm-5.3-abliterated"
  }
}
```

**Codex**, `~/.codex/dagon.config.toml`, a profile layered over `config.toml` by `--profile dagon` ([Codex profiles](https://developers.openai.com/codex/config-advanced#profiles)). Web search is off: the gateway does not run it.

```
model_provider = "dagon"
model = "glm-5.3-abliterated"
web_search = "disabled"

[features]
multi_agent = false

[model_providers.dagon]
name = "Dagon Inference"
base_url = "https://inference.dagon.so/v1"
env_key = "DAGON_API_KEY"
wire_api = "responses"
```

## How do I test it?

```
# In the sandbox (boat ssh <id>), or wrap each line in boat exec <id> '...'
echo ${DAGON_API_KEY:+key set} ${DAGON_BASE_URL:+base URL set}

# The key, its balance and expiry. Free: nothing is generated
curl -sS "$DAGON_BASE_URL/key" -H "Authorization: Bearer $DAGON_API_KEY"

# One short paid request
curl -sS "$DAGON_BASE_URL/chat/completions" \
  -H "Authorization: Bearer $DAGON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "glm-5.3-abliterated", "max_tokens": 50, "messages": [{"role": "user", "content": "Say hi"}]}'
```

`GET /v1/key` answers `401` for a wrong, revoked or expired key, and `402 insufficient_credits` on a request means the key is short of the request's maximum cost: fund it or lower `max_tokens`.

## Who bills what?

- **boat** bills the sandbox: its time, per second, at the hourly rate of its size, from your plan's included time and then your boat wallet ([boat pricing](https://docs.boat.dev/pricing)). Model tokens are not on the boat bill.
- **Dagon Inference** bills the tokens: each request is charged to the key's prepaid USDC balance at the serving provider's price plus our 10% markup, with a signed receipt. There is no subscription.
- boat's AI credentials proxy, if you turn it on, is a separate boat charge per day and per request ([AI credentials](https://docs.boat.dev/ai-credentials)). Nothing here needs it.

## How do I keep the key safe?

- **One child key per environment or per sandbox**, with a balance and an `expires_at`. A runaway agent stops at `402 insufficient_credits`, and a leaked key costs at most what is on it. A root holds at most 100 live children ([Child keys](https://inference.dagon.so/docs/child-keys)).
- **The root key stays on your computer.** Anyone with a shell in a sandbox can read its variables, so a teammate's or customer's sandbox should run on an environment marked safe for third parties, which gets none of your variables.
- **Rotate** by minting a new child, swapping the variable and deleting the old child:

```
# 1. Mint a new child key (above) and store it in place of the old one
printf %s "$NEW_DAGON_KEY" | boat env set-var dagon DAGON_API_KEY
# 2. Move running sandboxes to the new environment version
boat env upgrade dagon
# 3. Delete the old child; its unspent balance moves back to the root key
curl -X DELETE https://inference.dagon.so/v1/keys/children/$OLD_CHILD_ID -H "Authorization: Bearer $DAGON_ROOT_KEY"
```

- boat can also keep the real key out of the sandbox: a [custom API](https://docs.boat.dev/ai-credentials) named `DAGON_API_KEY` with the AI credentials proxy on gives the sandbox a placeholder key and a proxy URL in `DAGON_API_KEY_BASE_URL`. We have not tested Dagon or the harnesses behind that proxy.

## Common questions

### Can I run Claude Code on Dagon Inference in a boat sandbox?

Yes, on the Anthropic Messages endpoint: the setup script writes `~/.claude/settings.json` with the base URL `https://inference.dagon.so`, a key helper that prints `$DAGON_API_KEY` and a model. The gateway accepts the `anthropic-beta` values Claude Code sends and forwards none of them. The models are open-weight models, not Claude.

### Can I use Codex with Dagon Inference?

Yes, on Responses with `codex --profile dagon`. The setup profile disables web search and multi-agent. Optional web-search and namespace tools are omitted and listed in `x-dagon-dropped-tools`; forced unavailable choices are refused. The model cannot call omitted multi-agent or MCP tools. Codex's `client_metadata` is never forwarded.

### Does boat charge for the tokens?

No. boat bills the sandbox's time. Dagon Inference charges the tokens to the key's prepaid USDC balance. boat's optional AI credentials proxy is a separate boat charge; this setup does not use it.

### Where does the key live?

In a boat environment variable, `DAGON_API_KEY`, which boat puts into the shells of each new sandbox of that environment. The harness configs name the variable and never hold the key.

## Create a key in a minute.

[Get a key](https://inference.dagon.so/app/)

## Related guides

- [Uncensored OpenAI-compatible API for security research, refusals removed](https://inference.dagon.so/cybersecurity-llm-api)
- [Smart contract audit with an uncensored LLM](https://inference.dagon.so/smart-contract-audit-llm)
- [No-KYC LLM API: use it without KYC and pay with crypto (USDC)](https://inference.dagon.so/pay-for-llm-api-with-usdc)
- [Use an uncensored LLM with the OpenAI or Anthropic SDK](https://inference.dagon.so/uncensored-llm-openai-sdk)
- [Anthropic Messages API compatible endpoint for uncensored models](https://inference.dagon.so/anthropic-messages-api-uncensored-models)
- [Abliterated LLM API: models, providers and pricing](https://inference.dagon.so/abliterated-llm-pricing)
- [Providers that host abliterated (refusal-removed) open-weight models through an API](https://inference.dagon.so/abliterated-model-api-providers)
- [Prepaid LLM API key for AI agents, paid in USDC](https://inference.dagon.so/buy-llm-credits-from-an-agent-wallet)
- [x402 LLM API: AI agents pay per request in USDC on Solana](https://inference.dagon.so/x402-llm-api)
