Every API here is available over the APIs.io API and to AI agents over MCP.
{
"openapi": "3.1.0",
"info": {
"title": "Solvela Gateway",
"description": "Solana-native AI agent payment gateway. OpenAI-compatible LLM chat completions paid per request in USDC-SPL over the x402 protocol — no API key, no account, just a wallet. A rule-based smart router selects a model per request, an exact-match response cache returns prior answers at zero upstream cost, and a trustless on-chain escrow scheme is available for prepaid sessions.",
"version": "0.1.0",
"license": { "name": "BUSL-1.1", "identifier": "BUSL-1.1" },
"contact": { "email": "partnerships@solvela.ai", "url": "https://solvela.ai" },
"x-guidance": "Pay per request in USDC-SPL on Solana via x402 — no API key or account, just a wallet. POST /v1/chat/completions with no PAYMENT-SIGNATURE header to receive a 402 challenge quoting the USDC cost; sign the quoted `exact` (or `escrow`) payment and resubmit the same request with the signed payload in the PAYMENT-SIGNATURE header. Model catalog at GET /v1/models; x402 discovery at /openapi.json and /.well-known/x402."
},
"servers": [
{ "url": "https://api.solvela.ai", "description": "Production" },
{ "url": "https://solvela-gateway.fly.dev", "description": "Direct Fly host" }
],
"paths": {
"/v1/chat/completions": {
"post": {
"operationId": "createChatCompletion",
"summary": "Create an OpenAI-compatible chat completion",
"description": "OpenAI-compatible chat completion. Without a `PAYMENT-SIGNATURE` header the gateway returns HTTP 402 with an x402 challenge: a legacy snake_case JSON body plus a canonical x402 v2 challenge (camelCase) in the `PAYMENT-REQUIRED` response header, both quoting the USDC cost on Solana mainnet. Sign the quoted `exact` (or `escrow`) payment with your wallet and resubmit the same request with the signed payment payload in the `PAYMENT-SIGNATURE` header to receive the completion.",
"x-payment-info": {
"protocols": ["x402"],
"price": { "mode": "dynamic", "currency": "USD", "min": 0.000001, "max": 1.00, "description": "Non-binding discovery hint; the authoritative per-request price is the dynamic x402 402 challenge (mode=dynamic). Real cost depends on the resolved model and token counts." }
},
"security": [{}, { "x402Payment": [] }],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/ChatCompletionRequest" },
"example": {
"model": "auto",
"messages": [
{ "role": "user", "content": "What is 2+2?" }
]
}
}
}
},
"responses": {
"200": {
"description": "Chat completion (or an SSE stream when `stream` is true).",
"headers": {
"X-Solvela-Receipt": {
"description": "Path of the retrievable payment receipt (`/v1/receipts/{receipt_id}`) for this PAID completion. Present only when the request settled a payment AND the gateway has receipt storage configured; absent on free-tier ($0) responses and on gateways without a database. For SSE streams the header is decided before the body starts. The UUIDv4 id is a bearer capability — anyone holding it can read the receipt.",
"schema": { "type": "string" }
}
},
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/ChatCompletionResponse" }
},
"text/event-stream": {
"schema": { "type": "string", "description": "Server-sent events: `data: {chunk}` lines terminated by `data: [DONE]`." }
}
}
},
"402": {
"description": "Payment required. Two distinct bodies share this status: (1) the x402 **challenge** (`PaymentRequired`, snake_case fields) when the request carries no `PAYMENT-SIGNATURE` header — sign and resubmit; (2) the standard **error envelope** (`Error`, with `error.type` of `payment_required` or `invalid_payment`) when a payment header was present but could not be decoded or verified — do not blindly retry. The `PAYMENT-REQUIRED` response header accompanies the challenge form.",
"headers": {
"PAYMENT-REQUIRED": {
"description": "Base64-encoded canonical x402 v2 challenge as camelCase JSON (`x402Version`, `accepts[].payTo`, `accepts[].maxTimeoutSeconds`, …) — note the JSON *body* uses snake_case; the two casings are intentional and must not be mixed. Carries only `exact` scheme entries, each with `extra: {\"decimals\": 6}`. Present whenever an `exact` scheme is offered (i.e., on every challenge).",
"schema": { "type": "string" }
}
},
"content": {
"application/json": {
"schema": {
"anyOf": [
{ "$ref": "#/components/schemas/PaymentRequired" },
{ "$ref": "#/components/schemas/Error" }
]
}
}
}
},
"400": {
"description": "Invalid request (`error.type`: `bad_request`).",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
},
"404": {
"description": "Unknown model ID, alias, or profile (`error.type`: `model_not_found`).",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
},
"415": {
"description": "Image content sent to a model without vision capability (`error.type`: `unsupported_media_type`).",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
},
"429": {
"description": "Rate limited (`error.type`: `rate_limit_exceeded` or `rate_limited`). Honor `retry-after` before retrying.",
"headers": {
"retry-after": { "description": "Seconds until the rate-limit window resets.", "schema": { "type": "integer" } },
"x-ratelimit-limit": { "description": "Requests allowed per window.", "schema": { "type": "integer" } },
"x-ratelimit-remaining": { "description": "Requests remaining in the current window (always 0 on a 429).", "schema": { "type": "integer" } },
"x-ratelimit-reset": { "description": "Seconds until the window resets.", "schema": { "type": "integer" } }
},
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
},
"5XX": {
"description": "Upstream or gateway failure: 502 (`provider_error`), 503 (`upstream_unavailable` — all providers down), 500 (`settlement_failed`, `internal_error`).",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
}
}
}
},
"/v1/models": {
"get": {
"operationId": "listModels",
"summary": "List available models with pricing and capabilities",
"description": "Returns the model catalog with per-token pricing and capabilities. No payment required.",
"security": [],
"responses": {
"200": {
"description": "Model list.",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/ModelList" }
}
}
}
}
}
},
"/v1/receipts/{receipt_id}": {
"get": {
"operationId": "getReceipt",
"summary": "Fetch a payment receipt by id",
"description": "Returns the client-facing receipt for a paid request: payer wallet, payment scheme, transaction reference, and the amounts actually charged (atomic USDC integers are canonical; decimal strings are derived). The unguessable UUIDv4 receipt id — issued in the `X-Solvela-Receipt` response header on paid responses — is the only credential: treat it as a bearer capability. Unknown and malformed ids both return the same 404, and there is no listing endpoint. Free ($0) requests produce no payment and therefore no receipt.",
"security": [],
"parameters": [
{
"name": "receipt_id",
"in": "path",
"required": true,
"description": "UUIDv4 receipt id from the `X-Solvela-Receipt` header.",
"schema": { "type": "string", "format": "uuid" }
}
],
"responses": {
"200": {
"description": "The receipt.",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Receipt" }
}
}
},
"404": {
"description": "Unknown or malformed receipt id (`error.type` = `not_found`). The two cases are deliberately indistinguishable.",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Error" }
}
}
},
"429": {
"description": "Rate limited (`error.type`: `rate_limit_exceeded`). This public route carries a stricter per-client-IP cap than the generic limiter (default 20/min) to bound receipt-id scanning. Honor `retry-after` before retrying.",
"headers": {
"retry-after": { "description": "Seconds until the rate-limit window resets.", "schema": { "type": "integer" } },
"x-ratelimit-limit": { "description": "Requests allowed per window.", "schema": { "type": "integer" } },
"x-ratelimit-remaining": { "description": "Requests remaining in the current window (always 0 on a 429).", "schema": { "type": "integer" } },
"x-ratelimit-reset": { "description": "Seconds until the window resets.", "schema": { "type": "integer" } }
},
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
},
"503": {
"description": "Receipt storage is not configured on this gateway (`error.type` = `service_unavailable`) — receipts cannot exist here at all, so no per-id 404 is implied.",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Error" }
}
}
}
}
}
},
"/health": {
"get": {
"operationId": "health",
"summary": "Report gateway and dependency health status",
"description": "Liveness/readiness probe. No payment required.",
"security": [],
"responses": {
"200": {
"description": "Service status.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": { "status": { "type": "string", "enum": ["ok", "degraded", "error"] } },
"required": ["status"]
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"x402Payment": {
"type": "apiKey",
"in": "header",
"name": "PAYMENT-SIGNATURE",
"description": "x402 payment payload: JSON (raw or base64-encoded) of the form `{ x402_version, resource: {url, method}, accepted: <one entry from the 402 challenge's accepts[]>, payload: { transaction } | { deposit_tx, service_id, agent_pubkey } }`, where `transaction`/`deposit_tx` is a base64-encoded signed Solana versioned transaction. Omit the header to receive the 402 challenge quoting the price."
}
},
"schemas": {
"ChatCompletionRequest": {
"type": "object",
"required": ["model", "messages"],
"properties": {
"model": {
"type": "string",
"description": "Model ID (e.g. `openai/gpt-4o`), alias (e.g. `sonnet`), or routing profile (`auto`, `eco`, `premium`, `free`). Call `GET /v1/models` for available IDs.",
"example": "auto"
},
"messages": {
"type": "array",
"description": "Conversation messages in OpenAI chat format, ordered oldest to newest (roles: system, user, assistant, tool, developer).",
"minItems": 1,
"items": { "$ref": "#/components/schemas/ChatMessage" }
},
"max_tokens": { "type": "integer", "minimum": 1, "description": "Max output tokens; clamped to the model limit." },
"temperature": { "type": "number", "minimum": 0, "maximum": 2 },
"top_p": { "type": "number", "minimum": 0, "maximum": 1 },
"stream": { "type": "boolean", "default": false, "description": "Stream the response as Server-Sent Events." },
"tools": { "type": "array", "items": { "type": "object" }, "description": "OpenAI-style tool/function definitions." },
"tool_choice": { "description": "OpenAI-style tool choice." }
}
},
"ChatMessage": {
"type": "object",
"required": ["role"],
"properties": {
"role": { "type": "string", "enum": ["system", "user", "assistant", "tool", "developer"] },
"content": {
"description": "Plain string, an array of content parts (text and image_url) for vision-capable models, or null/absent on assistant turns that carry only `tool_calls`. The gateway maps absent and null content to the empty string on input.",
"anyOf": [
{ "type": "string" },
{ "type": "array", "items": { "type": "object" } },
{ "type": "null" }
]
},
"name": { "type": "string" },
"tool_calls": {
"type": "array",
"description": "Tool calls requested by the model (assistant messages only). Reply with a `role: tool` message carrying the matching `tool_call_id`.",
"items": { "$ref": "#/components/schemas/ToolCall" }
},
"tool_call_id": { "type": "string" }
}
},
"ToolCall": {
"type": "object",
"required": ["id", "type", "function"],
"properties": {
"id": { "type": "string", "description": "Unique identifier for this tool call; echo it back as `tool_call_id` on the follow-up `role: tool` message." },
"type": { "type": "string", "example": "function" },
"function": {
"type": "object",
"description": "The function the model wants invoked.",
"required": ["name", "arguments"],
"properties": {
"name": { "type": "string", "description": "Function name, matching a `tools[].function.name` from the request." },
"arguments": { "type": "string", "description": "JSON-encoded function arguments." }
}
}
}
},
"ChatCompletionResponse": {
"type": "object",
"required": ["id", "object", "created", "model", "choices"],
"properties": {
"id": { "type": "string" },
"object": { "type": "string", "example": "chat.completion" },
"created": { "type": "integer" },
"model": { "type": "string" },
"choices": {
"type": "array",
"items": {
"type": "object",
"required": ["index", "message"],
"properties": {
"index": { "type": "integer" },
"message": { "$ref": "#/components/schemas/ChatMessage" },
"finish_reason": { "type": ["string", "null"] }
}
}
},
"usage": {
"type": ["object", "null"],
"required": ["prompt_tokens", "completion_tokens", "total_tokens"],
"properties": {
"prompt_tokens": { "type": "integer" },
"completion_tokens": { "type": "integer" },
"total_tokens": { "type": "integer" }
}
}
}
},
"PaymentRequired": {
"type": "object",
"description": "x402 challenge returned with HTTP 402 when no `PAYMENT-SIGNATURE` header is present. Field names are snake_case; the canonical camelCase rendering travels in the `PAYMENT-REQUIRED` response header.",
"required": ["x402_version", "resource", "accepts", "cost_breakdown", "error"],
"properties": {
"x402_version": { "type": "integer", "example": 2 },
"resource": {
"type": "object",
"required": ["url", "method"],
"properties": {
"url": { "type": "string", "example": "/v1/chat/completions" },
"method": { "type": "string", "example": "POST" }
}
},
"accepts": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["scheme", "network", "amount", "asset", "pay_to", "max_timeout_seconds"],
"properties": {
"scheme": { "type": "string", "enum": ["exact", "escrow"] },
"network": { "type": "string", "example": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" },
"amount": { "type": "string", "description": "Atomic units (USDC has 6 decimals)." },
"asset": { "type": "string", "description": "USDC SPL mint address." },
"pay_to": { "type": "string", "description": "Recipient wallet address." },
"max_timeout_seconds": { "type": "integer", "example": 300 },
"escrow_program_id": { "type": "string", "description": "Escrow program ID. Present only on `scheme: escrow` entries; absent otherwise." }
}
}
},
"cost_breakdown": {
"type": "object",
"required": ["provider_cost", "platform_fee", "total", "currency", "fee_percent"],
"properties": {
"provider_cost": { "type": "string" },
"platform_fee": { "type": "string" },
"total": { "type": "string" },
"currency": { "type": "string", "example": "USDC" },
"fee_percent": { "type": "integer", "example": 5 }
}
},
"error": { "type": "string" },
"extensions": {
"type": "object",
"description": "Optional, additive discovery metadata. Present on the live `/v1/chat/completions` 402 challenge body; absent on the canonical camelCase `PAYMENT-REQUIRED` header. Carries the static Coinbase-Bazaar block (`extensions.bazaar`) so x402 discovery indexers (x402scan, agentcash) read the resource as invocable — a non-canonical challenge-embed because Solvela self-settles rather than running on Coinbase's facilitator. NOT part of the value path: clients sign `accepts`, never `extensions`; money fields, verification, and settlement are byte-unchanged. Identical on every challenge (no wallet/amount/time data).",
"properties": {
"bazaar": {
"type": "object",
"description": "Coinbase-Bazaar discovery descriptor: `info` (x402scan invocability gate) plus `schema` whose `properties.input.properties.body` is a JSON Schema of the chat request and `properties.output.properties.example` is a representative `chat.completion` response (agentcash schema extraction)."
}
}
}
}
},
"ModelList": {
"type": "object",
"required": ["object", "data"],
"properties": {
"object": { "type": "string", "example": "list" },
"data": {
"type": "array",
"items": {
"type": "object",
"required": ["id", "object", "provider", "display_name", "context_window", "capabilities", "pricing"],
"properties": {
"id": { "type": "string" },
"object": { "type": "string", "example": "model" },
"provider": { "type": "string" },
"display_name": { "type": "string" },
"context_window": { "type": "integer" },
"capabilities": {
"type": "object",
"required": ["streaming", "tools", "vision", "reasoning"],
"properties": {
"streaming": { "type": "boolean" },
"tools": { "type": "boolean" },
"vision": { "type": "boolean" },
"reasoning": { "type": "boolean" }
}
},
"pricing": {
"type": "object",
"required": ["input_per_million", "output_per_million", "currency", "fee_percent"],
"properties": {
"input_per_million": { "type": "number" },
"output_per_million": { "type": "number" },
"currency": { "type": "string", "example": "USDC" },
"fee_percent": { "type": "integer", "example": 5 }
}
}
}
}
}
}
},
"Receipt": {
"type": "object",
"description": "Client-facing payment receipt for a paid request. Atomic-USDC integers (6 decimals) are canonical; the `*_usdc` decimal strings are derived from them. `amount_paid_atomic` is what the payer was actually billed and equals `cost_breakdown.total_atomic` except when an escrow semantic-cache discount realised on-chain. It is the billed amount from the gateway ledger's perspective — identical to the spend ledger — and can differ from the raw on-chain transfer amount when an agent overpays the 402 quote.",
"required": ["receipt_id", "created_at", "model", "payment_scheme", "payer_wallet", "amount_paid_atomic", "amount_paid_usdc", "cost_breakdown"],
"properties": {
"receipt_id": { "type": "string", "format": "uuid" },
"created_at": { "type": "string", "format": "date-time", "description": "When the receipt was recorded (request completion time, UTC)." },
"model": { "type": "string", "description": "Model ID (chat path) or marketplace service ID (services proxy path).", "example": "openai/gpt-4o" },
"payment_scheme": { "type": "string", "description": "x402 scheme that settled the payment.", "example": "exact" },
"tx_signature": { "type": "string", "description": "Payment transaction reference as recorded on the spend ledger (the signed transaction carried in the payment payload). Absent when no reference was extractable." },
"payer_wallet": { "type": "string", "description": "Payer wallet (base58 pubkey) extracted from the signed payment." },
"amount_paid_atomic": { "type": "integer", "minimum": 0, "description": "Amount actually billed, atomic USDC. Canonical." },
"amount_paid_usdc": { "type": "string", "example": "0.002625" },
"cost_breakdown": { "$ref": "#/components/schemas/ReceiptCostBreakdown" },
"vendor": { "$ref": "#/components/schemas/ReceiptVendorSettlement" }
}
},
"ReceiptCostBreakdown": {
"type": "object",
"description": "Agent-facing cost breakdown that produced the bill: provider cost + platform fee = total. On vendor-settled services the agent fee is 0 (the vendor absorbs the platform fee — see `vendor`).",
"required": ["provider_cost_atomic", "provider_cost_usdc", "platform_fee_atomic", "platform_fee_usdc", "total_atomic", "total_usdc", "currency"],
"properties": {
"provider_cost_atomic": { "type": "integer", "minimum": 0 },
"provider_cost_usdc": { "type": "string", "example": "0.002500" },
"platform_fee_atomic": { "type": "integer", "minimum": 0 },
"platform_fee_usdc": { "type": "string", "example": "0.000125" },
"total_atomic": { "type": "integer", "minimum": 0 },
"total_usdc": { "type": "string", "example": "0.002625" },
"currency": { "type": "string", "example": "USDC" }
}
},
"ReceiptVendorSettlement": {
"type": "object",
"description": "Vendor-settlement evidence, present only when the request hit a marketplace service with a per-service `vendor_wallet`: the agent's transfer settled `settled_atomic` directly to the vendor on-chain, and Solvela's platform fee is recorded as an off-chain receivable against the vendor (never charged to the agent).",
"required": ["vendor_wallet", "settled_atomic", "settled_usdc", "fee_receivable_atomic", "fee_receivable_usdc"],
"properties": {
"vendor_wallet": { "type": "string", "description": "Vendor wallet (base58 pubkey) the payment settled to." },
"settled_atomic": { "type": "integer", "minimum": 0 },
"settled_usdc": { "type": "string", "example": "0.020000" },
"fee_receivable_atomic": { "type": "integer", "minimum": 0 },
"fee_receivable_usdc": { "type": "string", "example": "0.001000" }
}
},
"Error": {
"type": "object",
"required": ["error"],
"properties": {
"error": {
"type": "object",
"required": ["type", "message"],
"properties": {
"type": {
"type": "string",
"description": "Machine-readable error kind. Known values: `bad_request`, `model_not_found`, `not_found`, `payment_required`, `invalid_payment`, `settlement_failed`, `forbidden`, `unsupported_media_type`, `rate_limited`, `rate_limit_exceeded`, `provider_error`, `upstream_unavailable`, `service_unavailable`, `internal_error`. New values may be added; treat unknown values as retriable-or-not by HTTP status.",
"example": "invalid_payment"
},
"message": { "type": "string" }
}
}
}
}
}
}
}