Developers

An OpenAI-compatible API, paid in Fuel.

Point the openai SDK you already use at Dulus: one key, every hosted model. Each call holds its Fuel up front, settles on the tokens it really used, and hands you the exact charge in a header.

Base URL https://control.dulus.ai/v1
  • OpenAI request shape
  • Keys stored as SHA-256 hashes
  • Failed calls fully refunded
app.py openai
import os
from openai import OpenAI
 
client = OpenAI(
    base_url="https://control.dulus.ai/v1",
    api_key=os.environ["DULUS_API_KEY"],
)
 
reply = client.chat.completions.create(
    model="dulus-b-27b",
    messages=[{"role": "user", "content": "Hello"}],
)
200 OK X-Dulus-Model: dulus-b-27b X-Dulus-Fuel-Charged
The gold lines are all that is Dulus. The rest is the openai SDK, unchanged.

Quickstart

Your first call, in three steps.

A free Dulus account, a key and some Fuel. After that it is the OpenAI request you already know.

  1. Mint a key

    Sign in to the Dulus desktop app, open API Keys and press Create key. Copy the secret right away: it is shown once and stored only as a SHA-256 hash, so it can be revoked but never recovered.

    • Optional rate limit per key: 1–10,000 requests a minute.
    • Optional expiry: 1–3,650 days. Without one, a key works until you revoke it.
    • Keep it out of your code: export DULUS_API_KEY=dulus_sk_live_…
  2. Top up Fuel

    Hosted models are paid per token in Fuel, and 1 Fuel = 1 $DULUS. Deposit $DULUS on Solana to the personal deposit address in the app’s Fuel view; it is credited to your Fuel ledger with a 1% bonus.

    How Fuel works
  3. Make the call

    Swap in the base URL and your key; everything else is the OpenAI request. Copy it for curl, Python or Node.

curl

curl https://control.dulus.ai/v1/chat/completions \
  -H "Authorization: Bearer $DULUS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "dulus-b-27b",
    "messages": [
      {"role": "user", "content": "Hello from Dulus"}
    ]
  }'

Endpoints

Two endpoints, OpenAI-shaped.

The OpenAI paths and request shapes, under one base URL: https://control.dulus.ai/v1.

POST /v1/chat/completions

Needs a key

Chat, the OpenAI way. Send the request body you would send to OpenAI; the reply comes back in the same shape.

model
A hosted id, such as dulus-b-27b. An unknown id gets 404.
messages
OpenAI-style messages, from 1 to 512 per request.
tools, …
Extra fields like these are passed through to the model.

What every call goes through

  1. Reserve

    An upper bound of Fuel is held, atomically. Not enough Fuel? The call stops here with 402, before any model runs.

  2. Run

    The model answers your request.

  3. Settle

    The charge is computed from input, output and reasoning tokens. Cached tokens are free.

  4. Return

    The unused hold goes straight back to your balance. A failed call is refunded in full.

GET /v1/models

Public, no key needed

Every hosted id, in the OpenAI list format. Free-tier entries are flagged with dulus_free_tier, so a client can label them without guessing from the name.

curl https://control.dulus.ai/v1/models
Response 200 OK
{
  "object": "list",
  "data": [
    {
      "id": "dulus-a-9b",
      "object": "model",
      "created": 0,
      "owned_by": "dulus"
    },
    { "id": "dulus-b-27b", … },
    …
    {
      "id": "dulus-free-9b", …
      "dulus_free_tier": true,
      "dulus_free_daily_limit": 40
    }
  ]
}

Headers

Every response carries its receipt.

Authenticate with a bearer key, name the request if you like, and read the exact charge off the response.

You send

Authorization: Bearer dulus_sk_live_…
Required for chat completions. A missing, revoked or expired key gets 401.
Idempotency-Key optional
Your id for this call, up to 128 characters. Each id is accepted once per account, so a repeat of a billed call is refused with 409 instead of being charged twice.
X-Request-ID optional
Does the same job. If you send both, Idempotency-Key wins.

You get back

X-Dulus-Fuel-Charged
The exact Fuel this call settled at, as an integer in millionths of a Fuel. 0 on free-tier models.
X-Dulus-Request-ID
The id the call was billed under: yours, or one Dulus generated.
X-Dulus-Model
The hosted id that answered.
Request POST /v1/chat/completions
Authorization: Bearer dulus_sk_live_…
Content-Type: application/json
Idempotency-Key: invoice-2291-summary

{ "model": "dulus-b-27b", "messages": [ … ] }
Response 200 OK
Content-Type: application/json
X-Dulus-Request-ID: invoice-2291-summary
X-Dulus-Model: dulus-b-27b
X-Dulus-Fuel-Charged: <fuel>
Cache-Control: no-store

{
  "id": "chatcmpl-…",
  "object": "chat.completion",
  "model": "dulus-b-27b",
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "Hello! What are we building?"
    },
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 12,
    "completion_tokens": 9,
    "total_tokens": 21
  }
}

<fuel> stands for the integer your call settled at. Header names are case-insensitive.

Errors & limits

When it says no, it says why.

Errors come back as JSON with a detail string, and a refused or failed call never costs Fuel.

402 { "detail": "insufficient Fuel for this request" }
Status When What to do
401 The key is missing, revoked or expired. Check the Authorization: Bearer header, or mint a new key in the app.
402 Not enough Fuel to reserve the call. On a free-tier model: less than 1,000,000 $DULUS held as Fuel. Top up Fuel, then send the request again.
404 The model id is not one Dulus hosts. Pick an id from GET /v1/models.
409 This request id was already used for a billed call on your account. Send a fresh Idempotency-Key for a new call. The first response is not replayed.
429 Over your rate limit, 60 requests a minute per account by default, or past the free tier’s 40 messages per 24 hours. Wait the seconds in the Retry-After header.
502 503 The upstream model failed or is not available right now. Retry with a new request id. The failed call was refunded in full.
  • 60/ min

    Default rate limit per account. A key can carry its own, from 1 to 10,000 a minute.

  • 512messages

    The most a single chat completion can carry. At least one is required.

  • 40/ 24 h

    Free-tier messages, up to 2,048 output tokens each, while you hold at least 1,000,000 $DULUS as Fuel.

  • by design

    An API key cannot read your Fuel balance or mint other keys. That takes the signed-in Dulus app.

Models

Fast, balanced, or the big one.

Rates are relative, per token: a 2× model spends Fuel twice as fast as a 1× one. For the live list, ask GET /v1/models.

Copies the model id.

  1. 1×

  2. 2×

  3. 3×

  4. 4×

  5. 0×

    Holder free tier. Keep at least 1,000,000 $DULUS as Fuel in your Dulus balance and get 40 free messages every 24 hours, up to 2,048 output tokens each. The balance is held, never spent.

Click an id to copy it.

Compare every model

Script the agent

Script the agent itself.

The API gives you the models. The dulus CLI gives you the whole agent, with its tools, memory and permissions, from any shell script.

Print mode

One prompt in, the full agent at work, the reply out. Then it exits.

$ dulus -p "summarize the open TODOs in this repo"

A JSONL event stream

With --output json, stdout carries only JSONL events (step_start, text, step_finish, error) and everything meant for humans moves to stderr. The exit code is 1 when the turn fails.

--accept-all skips every permission prompt. Save it for prompts you trust.

$ dulus -p --accept-all --output json -- "add a test"
{"type": "step_start"}
{"type": "text", "part": {"text": "Added a test. It passes."}}
{"type": "step_finish", "part": {"reason": "stop", …}}

Slash commands

Runs a slash command and exits, with no REPL to open. Here, /status.

$ dulus -c "/status"

Extend it

  • MCP servers

    Connect servers over stdio, SSE or HTTP. The app’s MCP Market searches thousands, and offers a security review before every install.

  • Skills

    Agent Skills (SKILL.md) from the official Anthropic marketplace, skills.sh and more. The in-app Skills Market searches tens of thousands.

  • Plugins

    /plugin install name@git_url adds tools, skills and MCP configs, with hot reload.

Mint a key. Make the call.

Get Dulus, open API Keys, and point your client at control.dulus.ai/v1. Your first reply is a few lines away.

esc

Actions