API reference
Base URL https://api.harnessnetwork.ai. The full OpenAPI document lives at api/openapi.yaml in the repository.
Authentication
Send Authorization: Bearer ak_<prefix>.<secret>. Keys are created on the buyer page and stored hashed; only the prefix is ever shown again. Every response carries X-Request-Id; quote it when reporting a problem.
GET /v1/catalog
The provider and model registry: every provider (OpenAI, Anthropic, DeepSeek, local GPU, mock test workers, plus the legacy auto tiers), each model's id, capabilities, network caps, list price (the cap: the provider's public price, or a network reference price for auto tiers and self-hosted models), the network price buyers pay now (list price times a multiplier, never above list, with discount_bps below list) and how many workers serve it right now. Prices are micro-USD per million tokens; buyers pay in USDC. Model ids are <provider>/<model>. A job for a model is only offered to nodes that advertise exactly that model. GET /v1/catalog/providers and GET /v1/catalog/providers/:id return slices.
{
"object": "catalog",
"model_id_format": "<provider>/<model>",
"providers": [{
"id": "anthropic",
"display_name": "Anthropic (Claude)",
"kind": "hosted_api",
"adapters": ["anthropic_api", "claude_plan"],
"online_workers": 1,
"models": [{
"id": "anthropic/claude-sonnet-5-5",
"model": "claude-sonnet-5-5",
"display_name": "Claude Sonnet 5.5",
"capabilities": ["chat", "stream", "json", "code", "reasoning"],
"context_window": 32000,
"max_output_tokens": 4096,
"pricing": {
"input_usd_per_million": 0.9, "output_usd_per_million": 4.5, "basis": "at_or_below_list",
"list_price": { "input_uusd_per_million": 3000000, "output_uusd_per_million": 15000000, "source": "https://www.anthropic.com/pricing", "as_of": "2026-10-20" },
"price": { "input_uusd_per_million": 900000, "output_uusd_per_million": 4500000, "minimum_charge_uusd": 500, "max_job_uusd": 5000000, "pricing_version": "..." },
"multiplier": 0.3,
"discount_bps": 7000
},
"online_workers": 1
}]
}]
}GET /v1/models
Lists network models (provider models and auto tiers) that have at least one admitted, ready worker right now. Models with no supply are omitted rather than listed as available; use /v1/catalog for the full list.
{
"object": "list",
"data": [{
"id": "mock/mock-1",
"object": "model",
"provider": "mock",
"display_name": "Mock (test workers) · Mock worker",
"owned_by": "decentralized-network",
"context_window": 16000,
"max_output_tokens": 2048,
"supports_stream": true,
"estimated_available_tpm": 12000,
"online_workers": 3
}]
}POST /v1/chat/completions
OpenAI-compatible subset: model (<provider>/<model> such as openai/gpt-6-sol, anthropic/claude-sonnet-5-5, deepseek/deepseek-flash; or a bare model id with provider; legacy network:text-standard still means "any provider"), messages (roles system, developer, user, assistant; string content only), max_tokens, temperature, stream, stream_options.include_usage, response_format (text or json_object). web_search_options (or network.web_search) asks for an answer that can search the web: it runs only on Claude and ChatGPT subscription devices whose owners allow it, costs a flat $0.01 more when the job completes, and returns NO_CAPACITY when none of those devices is online. Send Idempotency-Key; the same key with the same body returns the same job, a different body returns 409.
POST https://api.harnessnetwork.ai/v1/chat/completions
{
"model": "anthropic/claude-sonnet-5-5", // or "provider": "anthropic", "model": "claude-sonnet-5-5"
"messages": [{ "role": "user", "content": "Explain TLS in three bullets." }],
"max_tokens": 256,
"stream": true,
"network": {
"data_tier": "public", // public | trusted | encrypted_relay
"max_price_uusd": 50000, // optional cap on the quote
"max_latency_ms": 8000,
"allow_fallback": true, // retry another node if the first declines
"web_search": false // true: the answer may use the provider's hosted web search (+$0.01)
}
}Streaming responses are text/event-stream with chat.completion.chunk events ending in data: [DONE]. The last chunk carries usage, with network_price (the price the job locked when it started: multiplier, network and list prices, and discount_bps below list), and a network block with the job id, usage source and charge in micro-USD (USDC base units). Responses also carry the multiplier in the X-Price-Multiplier header.
data: {"id":"job_...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":"TLS"},"finish_reason":null}]}
data: {"id":"job_...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],
"usage":{"prompt_tokens":14,"completion_tokens":261,"total_tokens":275,
"network_price":{"multiplier":0.3,"input_uusd_per_million":900000,"output_uusd_per_million":4500000,
"list_input_uusd_per_million":3000000,"list_output_uusd_per_million":15000000,"discount_bps":7000}},
"network":{"job_id":"...","usage_source":"NETWORK_ESTIMATED","charged_uusd":"1188","charged_token_units":"1188"}}
data: [DONE]Credits are reserved for the maximum possible charge before dispatch and released to the measured amount on completion. A failure before the provider is invoked is never charged. A failure after output started returns an SSE error event with PARTIAL_STREAM and still terminates with [DONE]; the stream is never spliced with another worker's continuation.
Jobs, balance, pricing
- GET /v1/jobs/{id}
- status, token counts, charge, usage source, receipt hash, epoch, and the locked network_price
- POST /v1/jobs/{id}/cancel
- best-effort cancel; consumption until cancellation may be charged
- GET /v1/credits/balance
- available, reserved, withdrawal pending (USDC base units as decimal strings; 1000000 is $1)
- GET /v1/pricing
- per model: list price, network price, multiplier and updated_at, each card with an immutable pricing version
- POST /v1/deposits/intent
- vault address, asset, amount and a client id to pass to deposit()
Errors
| Code | HTTP | Meaning |
|---|---|---|
| INVALID_REQUEST | 400 | Schema violation; the message names the field. |
| UNSUPPORTED_PARAMETER | 400 | A parameter we do not implement (tools, logprobs, images). Never silently dropped. |
| UNAUTHORIZED | 401 | Missing or revoked API key. |
| INSUFFICIENT_BALANCE | 402 | Available credits do not cover the quote. |
| SPEND_AUTH_REQUIRED | 402 | No active signed spend authorization covers this job. |
| IDEMPOTENCY_CONFLICT | 409 | Same Idempotency-Key with a different body. |
| QUOTE_EXPIRED | 409 | The locked quote expired before dispatch; retry. |
| RATE_LIMITED | 429 | Per key, user or IP limit. Honor Retry-After. |
| NO_CAPACITY | 503 | No eligible node for the requested model and placement. Retryable. |
| MODEL_UNAVAILABLE | 503 | The model is off or has no admitted workers. |
| PROVIDER_COOLDOWN | 503 | The selected adapter is cooling down after an upstream 429. |
| NODE_DISCONNECTED | 503 | The worker dropped before the first byte. Retryable. |
| TIMEOUT_BEFORE_FIRST_TOKEN | 504 | No first token within the deadline. Nothing charged. |
| PARTIAL_STREAM | 502 | The stream failed after output started. Partial text was delivered; measured usage may be charged. |
| CLIENT_ABORTED | 499 | You closed the connection; consumption until cancellation may be charged. |
| PRICING_FROZEN | 503 | The operator froze pricing; no new quotes. |
| KILL_SWITCH | 503 | Inference is paused network-wide or for this model. |
{"error":{"code":"NO_CAPACITY","message":"No eligible node for requested model","request_id":"req_...","retryable":true}}Encrypted relay mode
With the @ainet/sdk client, createEncrypted() asks the gateway for a worker, verifies the worker's ephemeral X25519 key against its Ed25519 identity, derives a session key (HKDF-SHA256) and encrypts the prompt with XChaCha20-Poly1305. Chunks come back encrypted; the SDK decrypts and checks the response hash against the worker's receipt.
- The relay sees metadata for scheduling and billing (sizes, timing, token counts) and never the plaintext.
- The selected worker and its upstream model provider do see the plaintext. This mode is not confidentiality against the worker.
- Plain OpenAI clients cannot use this mode; it needs the SDK. For sensitive data choose data_tier: trusted and an audited or self-operated node pool.
Retry guidance
- 429 and 503: wait for Retry-After, then resend with the same Idempotency-Key. You will not be billed twice.
- 504 before the first token: nothing was charged; resend.
- 502 partial stream: keep what you received; a resend is a new job and a new charge.
- 402: fund the balance or sign a new spend authorization on the buyer page.