Antimatter

Docs

Agents and tools

Let an agent make its own key and pay its own way, and point the tools you already use at Antimatter.

For agents

An agent does not need a person at a dashboard. It can make its own key with a wallet signature, or skip the key and pay for each request with x402. Keys, prices and limits are the same as everyone's: see pricing and what the API keeps. /llms.txt is this page in a form written for agents.

A key from a wallet signature

Ask for the sign in message, sign it, exchange the signature for a session, then make a key. This needs Node 22 or later with viem, and any EVM wallet key will do, and a new wallet starts on the $0.25 free allowance.

import { privateKeyToAccount } from "viem/accounts";

const BASE = "https://useantimatter.com";
const wallet = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY);

// 1. The exact sign in message for this wallet.
const { message } = await fetch(`${BASE}/api/auth/nonce?address=${wallet.address}`).then((r) =>
  r.json(),
);

// 2. Sign it and exchange the signature for a session.
const signature = await wallet.signMessage({ message });
const verify = await fetch(`${BASE}/api/auth/verify`, {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ message, signature }),
});
const cookie = verify.headers
  .getSetCookie()
  .map((c) => c.split(";")[0])
  .join("; ");

// 3. Make a key with the session. It is shown once, so save it.
const minted = await fetch(`${BASE}/api/keys`, {
  method: "POST",
  headers: { "content-type": "application/json", cookie },
  body: JSON.stringify({ name: "my-agent" }),
}).then((r) => r.json());
console.log(minted.key); // save it as ANTIMATTER_API_KEY

Pay per request with x402

This is closed while $ANTI is the only way to pay. x402 settles USDC, so a request with no key that carries a payment is refused with a 401 that says so. When it is on, a request with no key gets a 402 reply with its price in USDC, and this is the whole client:

import { randomBytes } from "node:crypto";
import { privateKeyToAccount } from "viem/accounts";

const BASE = "https://useantimatter.com";
const wallet = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY); // holds USDC
const b64 = (v) => Buffer.from(JSON.stringify(v)).toString("base64");
const unb64 = (s) => JSON.parse(Buffer.from(s, "base64").toString());

const url = `${BASE}/v1/chat/completions`;
const body = JSON.stringify({
  model: "inclusionai/ling-3.0-tiny",
  max_tokens: 200,
  messages: [{ role: "user", content: "Hello" }],
});
const headers = { "content-type": "application/json" };

// 1. Ask without a key. The answer is 402 with the price on each network.
let res = await fetch(url, { method: "POST", headers, body });
if (res.status === 402) {
  const required = unb64(res.headers.get("payment-required"));
  const accepted = required.accepts[0]; // USDC on the first accepted network
  const chainId = Number(accepted.network.split(":")[1]);
  const now = Math.floor(Date.now() / 1000);
  const authorization = {
    from: wallet.address,
    to: accepted.payTo,
    value: accepted.amount,
    validAfter: String(now - 60),
    validBefore: String(now + accepted.maxTimeoutSeconds),
    nonce: `0x${randomBytes(32).toString("hex")}`,
  };

  // 2. Sign an EIP-3009 transfer of exactly that amount. Nothing moves yet.
  const signature = await wallet.signTypedData({
    domain: {
      name: accepted.extra.name,
      version: accepted.extra.version,
      chainId,
      verifyingContract: accepted.asset,
    },
    types: {
      TransferWithAuthorization: [
        { name: "from", type: "address" },
        { name: "to", type: "address" },
        { name: "value", type: "uint256" },
        { name: "validAfter", type: "uint256" },
        { name: "validBefore", type: "uint256" },
        { name: "nonce", type: "bytes32" },
      ],
    },
    primaryType: "TransferWithAuthorization",
    message: {
      ...authorization,
      value: BigInt(authorization.value),
      validAfter: BigInt(authorization.validAfter),
      validBefore: BigInt(authorization.validBefore),
    },
  });

  // 3. Send the same request again with the payment attached.
  const payment = { x402Version: 2, resource: required.resource, accepted, payload: { signature, authorization } };
  res = await fetch(url, {
    method: "POST",
    headers: { ...headers, "payment-signature": b64(payment) },
    body,
  });
}
console.log(res.status, (await res.json()).choices?.[0]?.message?.content);
console.log(unb64(res.headers.get("payment-response") ?? "e30=")); // the settlement

An agent wallet

Give an agent a budget of its own rather than your key. POST /v1/agents, with a key holding the agents scope, moves money from your balance to a new agent (two ledger rows, never new money; a free allowance cannot be moved) and returns the agent's own key once. That key spends the agent's balance and nothing else, under its policy: the models or Autopilot ids it can use, a ceiling per request, a daily cap, an expiry, and auto refill from you up to a daily ceiling you set. When it is empty it is refused with 402 agent_balance_exhausted. When it is paused it is refused at once with agent_paused. GET /v1/agents/{id} shows its balance, spend by day and model and its last requests. You can top it up, drain it, rotate its key or delete it, which returns what is left. An agent can fund itself: once its wallet signs the message from /v1/agents/self/wallet (a sign in style message naming this domain, the agent and its owner, with a nonce that expires; send the nonce back with the signature), a deposit from it claimed at /v1/agents/self/deposits lands on the agent. The dashboard does the same.

const BASE = "https://useantimatter.com";
const KEY = process.env.ANTIMATTER_API_KEY; // a key with the agents scope
const call = (path, body) =>
  fetch(`${BASE}${path}`, {
    method: body ? "POST" : "GET",
    headers: { authorization: `Bearer ${KEY}`, "content-type": "application/json" },
    body: body ? JSON.stringify(body) : undefined,
  }).then((r) => r.json());

// 1. An agent with $5 moved from this account, a $1 daily cap and auto refill.
const agent = await call("/v1/agents", {
  name: "researcher",
  fund_micros: 5_000_000,
  models: ["inclusionai/ling-3.0-tiny", "antimatter/auto"],
  max_cost_per_request_micros: 50_000,
  daily_cap_micros: 1_000_000,
  auto_refill: { amount_micros: 1_000_000, daily_max_micros: 2_000_000 },
});
console.log(agent.api_key); // the agent's own am_live_ key, shown once

// 2. The agent uses its key like any client; it spends its balance and nothing else.
// 3. See what it spent, then move what is left back.
const seen = await call(`/v1/agents/${agent.id}`);
console.log(seen.balance_micros, seen.spend_by_model);
await call(`/v1/agents/${agent.id}/drain`, {});

MCP

https://useantimatter.com/mcp is a hosted MCP server, Streamable HTTP, with the key as a bearer header. Its tools: antimatter_models (the catalog at billed prices), antimatter_cheapest (the cheapest models for a need), antimatter_estimate (what a request would cost, sending nothing), antimatter_chat (one completion, billed like /v1/chat/completions under the key's policy), antimatter_ensemble (several models and one combined answer, as /v1/ensemble) and antimatter_credits, and for agent wallets antimatter_agent_create, antimatter_agents and antimatter_agent_top_up, which never return your own key. Chat and ensemble need the key's chat scope, credits its read scope and the agent tools its agents scope. Every call counts against the key's 60 requests a minute, and an ensemble counts one per member. It is stateless, with no session, and every answer is JSON.

claude mcp add --transport http antimatter https://useantimatter.com/mcp \
  --header "Authorization: Bearer $ANTIMATTER_API_KEY"

Cursor reads .cursor/mcp.json. Claude Desktop's config file starts local servers, so it reaches this one through the mcp-remote bridge. A Claude Code plugin that adds the server and two skills is in the repository, under packages/claude-plugin. It is not listed on any marketplace. The stdio MCP server package is built and not yet published.

In your tools

Every tool below already uses the OpenAI or Anthropic API, so it only needs the gateway's address, a key in ANTIMATTER_API_KEY and a model id. We read each setup from the tool's own documentation on 2026-10-29, linked under it, and each one says whether we ran it against an Antimatter server that day. You can convert code from OpenAI's or OpenRouter's own examples in the browser at /convert.

ToolRun hereWhat happened
Claude Code2.1.312claude -p answered through /v1/messages. Before this day's fix it was refused, because Claude Code sends a system message inside the conversation. The gateway now takes it.
Cursornot runCursor's chat is an app and its cursor-agent CLI takes no base URL, so we could not run it here.
Clinenot runCline is a VS Code extension and is not installed here.
Continuenot runContinue is an editor extension and is not installed here.
Aider0.86.2aider --message answered and counted the tokens the gateway reported.
Codex CLInot runCodex is not installed here.
OpenAI Agents SDKopenai-agents 0.24.0 (Python)An agent answered over chat completions and over the Responses API. A model given as a bare string lost its openai/ prefix, which the SDK reads as its own, so the example below uses a model object.
LangChainlangchain-openai 1.7.3 (Python)invoke and stream both answered.

Claude Coderun here, 2.1.312

Claude Code uses the Anthropic API, so it points at the gateway's origin without /v1 and sends the key as a bearer token. Set these in the shell that starts it:

export ANTHROPIC_BASE_URL="https://useantimatter.com"
export ANTHROPIC_AUTH_TOKEN="$ANTIMATTER_API_KEY"
export ANTHROPIC_MODEL="anthropic/claude-sonnet-4.5"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="anthropic/claude-sonnet-4.5"
claude

Keep the key out of a committed .claude/settings.json. Claude Code warns that it does not recognise an id it was not built with and assumes a 200k window; its model configuration page says how to set the window. The Claude Code plugin in the repository (packages/claude-plugin) is not published to any marketplace yet.

Read from code.claude.com/docs/en/llm-gateway-connect on 2026-10-29.

Cursornot run here

In Cursor's settings, under Models, put the key in the OpenAI API key field, switch on the override for the OpenAI base URL and give it the gateway, then add the model id by name:

OpenAI API Key            your key, as in ANTIMATTER_API_KEY
Override OpenAI Base URL  https://useantimatter.com/v1
Model                     inclusionai/ling-3.0-tiny

Cursor's own page says a custom key works with chat models only, and that requests go through Cursor's servers first, so the gateway must be reachable from the internet. A localhost URL will not work. The base URL override is in the app's settings, and that page does not describe it.

Read from cursor.com/help/models-and-usage/api-keys on 2026-10-29.

Clinenot run here

Choose the OpenAI Compatible provider in Cline's settings and fill in:

API Provider  OpenAI Compatible
Base URL      https://useantimatter.com/v1
API Key       your key, as in ANTIMATTER_API_KEY
Model ID      inclusionai/ling-3.0-tiny

Cline asks for the model's context window and prices by hand for this provider; the model's page has both.

Read from docs.cline.bot/provider-config/openai-compatible on 2026-10-29.

Continuenot run here

Add a model to ~/.continue/config.yaml with the openai provider and the gateway's base URL:

name: Antimatter
version: 0.0.1
schema: v1
models:
  - name: Antimatter
    provider: openai
    model: inclusionai/ling-3.0-tiny
    apiBase: https://useantimatter.com/v1
    apiKey: ${{ secrets.ANTIMATTER_API_KEY }}
    useResponsesApi: false
    roles:
      - chat
      - edit
      - apply

Continue sends some OpenAI model names over the Responses API. useResponsesApi: false keeps every model on chat completions.

Read from docs.continue.dev/customize/model-providers/top-level/openai on 2026-10-29.

Aiderrun here, 0.86.2

Aider reaches any OpenAI compatible endpoint through two variables, and the model is the gateway's id behind Aider's own openai/ prefix:

export OPENAI_API_BASE="https://useantimatter.com/v1"
export OPENAI_API_KEY="$ANTIMATTER_API_KEY"
aider --model openai/inclusionai/ling-3.0-tiny

The doubled prefix is right: the first openai/ tells Aider which API to use and is taken off before the request.

Read from aider.chat/docs/llms/openai-compat.html on 2026-10-29.

Codex CLInot run here

Codex takes a provider in ~/.codex/config.toml (a project's own config cannot add one). Codex uses only the Responses API, which the gateway serves:

model = "inclusionai/ling-3.0-tiny"
model_provider = "antimatter"

[model_providers.antimatter]
name = "Antimatter"
base_url = "https://useantimatter.com/v1"
env_key = "ANTIMATTER_API_KEY"
wire_api = "responses"

The gateway's Responses API is stateless. A request that asks it to store a response or continue one by id is refused and names the field. Not checked against Codex itself.

Read from learn.chatgpt.com/docs/config-file/config-reference on 2026-10-29.

OpenAI Agents SDKrun here, openai-agents 0.24.0 (Python)

Hand the agent a model object built on a client that points at the gateway. A bare string would do for most ids, but the SDK reads a leading openai/ as its own provider prefix and takes it off:

import os
from openai import AsyncOpenAI
from agents import Agent, OpenAIChatCompletionsModel, Runner, set_tracing_disabled

client = AsyncOpenAI(base_url="https://useantimatter.com/v1", api_key=os.environ["ANTIMATTER_API_KEY"])
set_tracing_disabled(True)  # traces go to OpenAI's own platform otherwise

agent = Agent(
    name="Assistant",
    instructions="Answer briefly.",
    model=OpenAIChatCompletionsModel(model="inclusionai/ling-3.0-tiny", openai_client=client),
)
print(Runner.run_sync(agent, "Hello").final_output)

The TypeScript form follows the SDK's own configuration guide (openai.github.io/openai-agents-js/guides/config) and was not run here.

Read from openai.github.io/openai-agents-python/config/ on 2026-10-29.

LangChainrun here, langchain-openai 1.7.3 (Python)

ChatOpenAI takes the gateway as its base URL, and stream_usage keeps the token counts on a stream:

import os
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="inclusionai/ling-3.0-tiny",
    base_url="https://useantimatter.com/v1",
    api_key=os.environ["ANTIMATTER_API_KEY"],
    stream_usage=True,
)
print(llm.invoke("Hello").content)

The TypeScript form was not run here.

Read from docs.langchain.com/oss/python/integrations/chat/openai on 2026-10-29.