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

CodeHTTPMeaning
INVALID_REQUEST400Schema violation; the message names the field.
UNSUPPORTED_PARAMETER400A parameter we do not implement (tools, logprobs, images). Never silently dropped.
UNAUTHORIZED401Missing or revoked API key.
INSUFFICIENT_BALANCE402Available credits do not cover the quote.
SPEND_AUTH_REQUIRED402No active signed spend authorization covers this job.
IDEMPOTENCY_CONFLICT409Same Idempotency-Key with a different body.
QUOTE_EXPIRED409The locked quote expired before dispatch; retry.
RATE_LIMITED429Per key, user or IP limit. Honor Retry-After.
NO_CAPACITY503No eligible node for the requested model and placement. Retryable.
MODEL_UNAVAILABLE503The model is off or has no admitted workers.
PROVIDER_COOLDOWN503The selected adapter is cooling down after an upstream 429.
NODE_DISCONNECTED503The worker dropped before the first byte. Retryable.
TIMEOUT_BEFORE_FIRST_TOKEN504No first token within the deadline. Nothing charged.
PARTIAL_STREAM502The stream failed after output started. Partial text was delivered; measured usage may be charged.
CLIENT_ABORTED499You closed the connection; consumption until cancellation may be charged.
PRICING_FROZEN503The operator froze pricing; no new quotes.
KILL_SWITCH503Inference 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.