# Charis PIM — best-execution LLM inference, settled in USDC on Base > OpenAI-compatible API that auctions every request across verified suppliers > (Charis peers / OpenRouter / Venice) and fills at the cheapest live price. > Display price == charged price, receipts are on-chain-settled, encrypted lane > is TEE-attested. This file is for LLM agents/integrators; humans see > https://charis.im/docs Base URL: https://gateway.charis.im Key dashboard (mint/manage): https://charis.im/usage Live market data & analytics: https://charis.im/analytics Pricing sheet (no JS, no auth): https://charis.im/pricing (markdown: /pricing.md, JSON: GET /v1/pricing — best $/1M per model/lane + 14-day history) ## Authentication — wallet-bound API keys Keys are bound to an EVM wallet; every request settles against that wallet's prepaid USDC vault. Minting requires >= $1 in the vault on the chain you name. Easiest: connect a wallet at https://charis.im/usage -> Generate API key (one gasless signature). The full key (pim_sk_...) is shown ONCE. Programmatic mint (SIWE-style, no gas): 1. GET /v1/auth/nonce?address=0xYOURWALLET -> { message } 2. personal_sign(message) with the wallet key 3. POST /v1/auth/verify { "address": "0x..", "signature": "0x..", "chain": "base" } -> { "api_key": "pim_sk_..." } (402 = vault below $1: deposit, retry same nonce) Chains: "base" (mainnet, real USDC — default for production), "base-sepolia" (testnet). A key REMEMBERS the chain it was minted on: requests without an X-Chain header use the key's chain automatically. Send X-Chain only to override. ## Chat — OpenAI-compatible POST /v1/chat/completions Authorization: Bearer pim_sk_... Content-Type: application/json X-Limit-All-In: 5.00 (optional: price ceiling, USD per 1M blended all-in tokens) { "model": "claude-sonnet-5:public", "max_tokens": 512, "messages": [ { "role": "user", "content": "..." } ] } Model ids are "{model}:{tier}" — tiers: public (best price), tee (encrypted, TEE-attested; see /v1/attestation/report). List everything at GET /v1/models (includes live per-source prices, all-in prices, 24h volume, and per-source `capabilities`). Filter by capability: GET /v1/models?supports=reasoning_control, prompt_caching (only models with >=1 live source supporting ALL listed). ## Request parameters — support matrix (param x source) Whitelisted OpenAI-style params are FORWARDED upstream; a request that USES a capability routes only to sources that can honor it (no silent drops — if no live source for the grade qualifies you get HTTP 400 pointing at /v1/models?supports=...). param | openrouter | venice | charis peer (AntSeed) ----------------------|------------|-------------------|-------------------------- temperature, top_p, | honored | honored | honored (peer-dependent) stop, seed, | | | frequency_penalty, | | | presence_penalty | | | response_format | honored | model-dependent* | unverified* tools / tool_choice | honored | model-dependent* | unverified* reasoning {effort, | honored | mapped: enabled= | mapped: chat_template_ max_tokens, exclude, | (verbatim) | false -> thinking | kwargs.enable_thinking enabled} | | disable+strip | (peer-dependent) chat_template_kwargs | dropped | dropped | honored (passed through) cache_control blocks | honored | not supported* | latency-only* max_tokens | capped by server (PIM cap), then honored on every source * = the capability flag on that source is "unverified"/"latency-only"/false — requests USING that param route away from these sources automatically. Per-source flags in /v1/models: "capabilities": {reasoning_control, prompt_caching, tools, json_mode} -> true | false | "unverified" | "latency-only". "unverified" is treated as false by routing (fail-closed) until the source passes a probe. ## Knowledge plane — NOT offered (files / vector stores / Responses API) Charis is an inference marketplace, not a document store. There is NO /v1/files, /v1/vector_stores, or /v1/responses (they 404 with this pointer). Chat requests carrying hosted tool types (file_search, web_search, code_interpreter, ...) are rejected 400 up front — nothing is charged. OpenAI vs_ stores cannot attach to Charis fills even for OpenAI-family models: the upstream account serving your fill is never the account that owns your store. The supported pattern — retrieve-then-generate (app-side): 1. Search your store where it lives (e.g. OpenAI POST /v1/vector_stores/{id}/search, or any local index). 2. Put the top-k chunks in a system message; put the STABLE corpus block first with cache_control on its last block (OpenRouter-routed fills bill cached reads at the discounted rate — see Prompt caching below). 3. POST /v1/chat/completions with ANY Charis model — your retrieval now works across OpenAI-family AND OSS suppliers at auction prices. ## Market-row honesty fields (/v1/models) - retail_ref: which OpenRouter reference the model's retail comparator carries — "or_cheapest_live" (the cheapest LIVE routable endpoint: the number a buyer could actually pay at OpenRouter right now) or "or_default" (the /models default-endpoint price); null when the market has no OpenRouter leg. - house: {source: true} marks house-operated charis-* legs — capacity Charis itself runs on an upstream account ("Charis supply + our 5% fee"). No per-fill on-chain peer payment is claimed for these legs; like every pseudonymous leg they price strictly below the model's retail ceiling. - quant: per-leg SELF-REPORTED quantization label ("Q8_0", "int4", ...; "unknown" when the source declares nothing). Disclosure only — Charis does not verify it, and it never filters routing. ## Coding agents (opencode, Claude Code, any agentic harness) Charis is a drop-in provider under agentic coding harnesses. Flow: get a key (email OTP at charis.im/usage, or SIWE) -> add credits (Stripe card or USDC) -> point the harness at the gateway -> agent loop runs with streaming tool calls -> each completion settles exactly once (deferred settlement) -> balance + receipts live at charis.im/usage and GET /v1/usage/{account}. Agent-scale limits: request bodies up to 10 MB with a VALID key (64 KB anonymous); up to 65536 output tokens per request (default 1024 when max_tokens is unset — agents should always set max_tokens); tool calls stream as delta.tool_calls fragments and the final choice carries finish_reason "tool_calls". Worst-case cost (max_tokens x output price) is reserved at admission — a thin balance 402s up front with exact numbers, never mid-run. Harnesses asking for a big max_tokens reserve a lot (Claude Code defaults to 32k ~= $0.54 on a $15/M model): cap it (CLAUDE_CODE_MAX_OUTPUT_TOKENS) or keep balance > max_tokens x output price. opencode (~/.config/opencode/opencode.json): { "$schema": "https://opencode.ai/config.json", "provider": { "charis": { "npm": "@ai-sdk/openai-compatible", "name": "Charis", "options": { "baseURL": "https://gateway.charis.im/v1", "apiKey": "{env:CHARIS_API_KEY}" }, "models": { "kimi-k3:public": { "name": "Kimi K3 (via Charis)", "limit": { "context": 200000, "output": 65536 } } } } } } Claude Code / Anthropic SDK agents — Anthropic Messages API at POST /v1/messages (+ POST /v1/messages/count_tokens returning the gateway's input ESTIMATE). The CHARIS catalog is the model namespace (graded ids; unknown ids 404 — no silent substitution): export ANTHROPIC_BASE_URL=https://gateway.charis.im export ANTHROPIC_AUTH_TOKEN=pim_sk_... export ANTHROPIC_MODEL="kimi-k3:public" export ANTHROPIC_SMALL_FAST_MODEL="kimi-k3:public" export CLAUDE_CODE_MAX_OUTPUT_TOKENS=4096 # caps the admission reservation Accepts both x-api-key and Authorization: Bearer. Streaming emits the Anthropic event grammar (message_start / content_block_start / content_block_delta with text_delta + input_json_delta / content_block_stop / message_delta / message_stop). Pick agent-ready models: GET /v1/models?supports=tools (tool requests route ONLY to tool-verified sources — fail-closed 400 when none qualifies for a model). ## Workflow orders — run a whole agent session below spot A resting bid for an ENTIRE multi-turn session against idle supplier capacity: commit "up to $X, at or below this all-in price, finished by deadline T". A supplier with spare GPUs accepts and posts a bond that secures DELIVERY BY THE DEADLINE (never output quality). Dollar-denominated because prompt caching makes token counts ~6x variable for identical work; the envelope meters what upstreams actually charge. Public lanes only. The loop: 1. POST /v1/workflow {"model": "kimi-k3:public", "budget_usd_max": 15, "ceiling_all_in": 11.25, "min_draw_usd": 1, "deadline_s": 1800, "fallback_spot": true} -> {"id": "wf-...", "reserved_usd": 15} The WHOLE budget escrows on placement; min_draw_usd is the non-refundable floor owed to the supplier for committing capacity. TRANCHE pricing (where enabled): pass "discount_pct": 25 INSTEAD of ceiling_all_in — it converts against the live spot at placement and FREEZES as your absolute ceiling (no live tracking afterwards). A discount-placed tranche may rest up to ~6.9 days (deadline_s), and takes optional split guard rails tokens_in_max / tokens_out_max. 2. TWO fill paths (tranches) — use either or both: PASSIVE: an OPEN order is drawable immediately — the frozen ceiling acts as the session's price limit. Whenever ANY live source quotes at or under it, the request fills at the LIVE price (<= ceiling); when spot rises back above it you get 402 ("pause") and the order KEEPS RESTING. No supplier, no bond — normal best-price routing under your limit. MANUAL: fill-wait GET /v1/workflow/{id} (Bearer) until status == "running" — a supplier accepted and posted the bond. Fills then route PINNED to that supplier and clear AT your ceiling (accepting IS consenting to serve there), even when their public ask is higher. Only operator-priced sources (registered suppliers, peers) can accept; a retail passthrough leg gets 422 by design — Charis is fee-only and never eats the spread. Accepting a partially-drawn order bonds only the undrawn remainder. Still "open" past your patience -> DELETE /v1/workflow/{id} to cancel free. 3. Run the harness with header X-Workflow-Id: wf-... on every chat/messages request. Requests draw the envelope (NOT your open balance) and never pay above your ceiling. Exhausted budget, token bound, or passed deadline -> 402 with the exact reason. opencode: provider options "headers": {"X-Workflow-Id": "wf-..."} Claude Code: export ANTHROPIC_CUSTOM_HEADERS="X-Workflow-Id: wf-..." 4. POST /v1/workflow/{id}/complete -> settles: supplier gets max(drawn, min_draw), unused budget releases to you, bond returns to the supplier. With tranches, an open order that drew passively completes the same way. Or let the runner drive the whole loop (place -> poll -> launch pinned -> complete): pim/tools/tranche_runner/run.sh in the repo — CHARIS_API_KEY=... DISCOUNT_PCT=25 HARNESS=opencode ./run.sh "your goal" (HARNESS=claude uses ANTHROPIC_BASE_URL + ANTHROPIC_CUSTOM_HEADERS instead.) If the supplier misses the deadline the order is SLASHED: you get the undrawn remainder back plus the bond's make-whole (with fallback_spot the remainder finishes at market and the bond covers spot-minus-agreed on it; without, the whole bond is the penalty). The bond is sized to your discount at accept time — (spot - ceiling)/ceiling of the envelope, 20% floor — so deep discounts are actually collateralized. Public demand book (no auth): GET /v1/workflow/book — anonymized bids (size, ceiling, deadline, expected_prefix_reuse; never buyer identity or work content) plus a committed-$ depth curve per price. My orders: GET /v1/workflow (Bearer). ## Reasoning control Send OpenRouter-style `"reasoning": {"effort": "low" | "high", "max_tokens": N, "exclude": bool, "enabled": bool}`. Routing constrains to reasoning_control:true sources (openrouter/venice today). Separated reasoning comes back as `message.reasoning` (buffered) / `reasoning_content` deltas (stream), and `usage.completion_tokens_details.reasoning_tokens` is always present (0 when none). ## Prompt caching — which routes cache, what you pay - OpenRouter-routed calls: REAL, billed caching. Anthropic-style `cache_control` blocks in messages are forwarded untouched (OpenAI models auto-cache with no markup). Cache hits show in `usage.prompt_tokens_details.cached_tokens` and the upstream discount flows into your charge (metering floors to the REAL discounted upstream cost — a cached read is billed cheaper, never full price). - Charis peers: vLLM prefix caching = faster repeats, NO billed discount — flagged "latency-only", never faked as a price cut. - Structure prompts cache-first: put the big stable context in the FIRST (system) message with `cache_control` on its last block; vary only the tail. - Requests carrying cache_control route only to prompt_caching:true sources. ## Latency control X-Max-Latency-S: — skip sources whose estimated completion time (max_tokens / measured tok/s) exceeds the budget. Default 90; 0 disables. Sources with unknown throughput are never skipped; if every source is too slow the cheapest still serves (a slow answer beats a 503). Response = standard OpenAI shape (choices, usage) plus x_charis: { "price_in_per_m": 1.32, "price_out_per_m": 6.60, // $/1M actually charged "cost_usd": 0.000298, // exact all-in charge "n_in": 12, "n_out": 64, // metered tokens "provider": "charis-74688e", // winning supplier "tx_hash": "0x...", // on-chain settlement tx "chain": "base", "receipt_id": "pim-...", "buyer_balance": 1.4993, // vault after this call "charis_units": 123 } // $CHARIS mined (see /charis) ## Encrypted lane (:tee) — full E2EE integration recipe An unsealed request to a :tee model is rejected with HTTP 400 — the gateway relays ONLY ciphertext (gateway-blind). The complete seal protocol (byte-validated against the live enclave; this is exactly what the charis.im app runs): CRYPTO secp256k1 ECDH -> HKDF-SHA256(salt="", info="ecdsa_encryption", L=32) -> AES-256-GCM (12B IV, 16B tag appended) KEYS uncompressed secp256k1 points: 65 bytes, `04` prefix, 130 lowercase hex chars. The ECDH value fed to HKDF is the 32-byte X-COORDINATE of the shared point. REQUEST message content = hex( client_pub(65) ‖ iv(12) ‖ ciphertext+tag ), one AES key derived from the enclave pubkey; send header X-Client-Pub-Key: . Enclave pubkey = `signing_public_key` from GET /v1/attestation/report?grade=:tee&source=venice RESPONSE { "e2ee": true, "chunks": [hex...], "x_charis": {...} } — each chunk = hex( server_eph_pub(65) ‖ iv(12) ‖ ct+tag ) with a FRESH per-chunk AES key = ECDH(client_priv, server_eph_pub). Chunks shorter than 154 hex chars, non-hex, or not `04`-prefixed are PLAINTEXT passthrough (whitespace tokens): keep as-is. Copy-paste (Node 20+; npm i @noble/curves @noble/hashes): import { secp256k1 } from '@noble/curves/secp256k1'; import { hkdf } from '@noble/hashes/hkdf'; import { sha256 } from '@noble/hashes/sha256'; import { bytesToHex, hexToBytes, utf8ToBytes } from '@noble/hashes/utils'; const GW = 'https://gateway.charis.im', MODEL = 'qwen3-4b:tee'; const att = await (await fetch(`${GW}/v1/attestation/report?grade=${MODEL}&source=venice`)).json(); const priv = secp256k1.utils.randomPrivateKey(); const pub = bytesToHex(secp256k1.getPublicKey(priv, false)); const aes = (peer) => hkdf(sha256, secp256k1.getSharedSecret(priv, peer, false).slice(1, 33), new Uint8Array(0), utf8ToBytes('ecdsa_encryption'), 32); // seal const iv = crypto.getRandomValues(new Uint8Array(12)); const k1 = await crypto.subtle.importKey('raw', aes(att.signing_public_key), 'AES-GCM', false, ['encrypt']); const ct = new Uint8Array(await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, k1, utf8ToBytes('What model are you?'))); const wire = pub + bytesToHex(iv) + bytesToHex(ct); // send const r = await (await fetch(`${GW}/v1/chat/completions`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.CHARIS_KEY}`, 'Content-Type': 'application/json', 'X-Client-Pub-Key': pub, 'X-Chain': 'base' }, body: JSON.stringify({ model: MODEL, messages: [{ role: 'user', content: wire }] }) })).json(); // open let out = ''; for (const c of r.chunks) { if (c.length < 154 || !/^[0-9a-f]+$/i.test(c) || !c.startsWith('04')) { out += c; continue; } const raw = hexToBytes(c); const k2 = await crypto.subtle.importKey('raw', aes(bytesToHex(raw.slice(0, 65))), 'AES-GCM', false, ['decrypt']); out += new TextDecoder().decode(await crypto.subtle.decrypt({ name: 'AES-GCM', iv: raw.slice(65, 77) }, k2, raw.slice(77))); } console.log(out, r.x_charis); // plaintext answer + on-chain settle receipt Human docs: https://charis.im/docs#doc-e2ee ## Cost controls - max_tokens: hard output cap (server default 1024, max 32768). Worst case cost = input_tokens x in_price + max_tokens x out_price (all prices per 1M). - X-Limit-All-In: fills only at-or-under your ceiling; otherwise HTTP 409 and NOTHING is charged (a limit order, not a market order). ## Limit orders (resting CLOB — public lanes only) POST /v1/orders { "model","prompt","limit_all_in","max_tokens","deadline_s" } Rests until a verified supplier fills at all_in <= limit_all_in ($/1M blended), then runs the prompt + settles. Both key types work: wallet-bound keys reserve + settle on-chain USDC; cloud (email) keys reserve + settle CREDITS — the order's worst case is escrow-held from your credit balance on placement, so you can only rest orders up to what you hold (402 tells you to deposit more). Released on cancel/expiry. GET /v1/orders your open + recent orders (Bearer) DELETE /v1/orders/{id} cancel + release the reservation (Bearer) GET /v1/orders/book public anonymized resting-bid book (no auth) — who bids what ## Image generation (PIM_IMAGES lane — single upstream, API-only) GET /v1/images/models image models + worst_image_usd (admission bound) and all_in_worst_usd (bound + acquisition + 2% fee) POST /v1/images { "model","prompt","n","resolution","aspect_ratio","quality", "output_format","seed","size" } (whitelist; no streaming) Synchronous; returns { data: [{b64_json, media_type}], usage, x_charis }. The worst case is reserved before the call; the EXACT upstream cost (+5.5% acquisition, +2% fee) settles once on success — a failed generation charges $0. Pays from the key's balance (credits or USDC; X-Pay-With works). PRIVACY NOTE: image prompts/outputs transit OpenRouter under its retention policy — no zero-data-retention guarantee and no encrypted lane for images. ## Key management (Authorization: Bearer required, same wallet) GET /v1/auth/keys -> masked list: name, prefix, spend, requests, limit, reset, expiration, last used PATCH /v1/auth/keys/{prefix} -> { "name": "prod", "disabled": false, "limit_usd": 5, "limit_reset": "monthly", "expires_ts": 1767225600 } (null clears) DELETE /v1/auth/keys/{prefix} -> revoke immediately Enforcement is server-side pre-fulfillment: disabled/expired -> 403, credit-limit hit -> 402 (the wallet vault itself is untouched). ## Settlement modes & payment sources Two per-KEY settings (PATCH /v1/auth/keys/{prefix}), plus a per-request header. Settlement mode — "settlement_mode": "classic" | "v2" (null clears -> classic) v2 (DEFAULT for keys minted on/after 2026-07-19) deferred on-chain settlement: the request is AUTHORIZED instantly (sub-second responses) and settled in a batch tx shortly after. V2 receipts carry x_charis.settlement = { "mode": "v2", "status": "authorized" | "settled", "batch_tx": "0x..." } Reconcile bounds: a batch settles at $10 accrued or 120s, whichever comes first. Track the lifecycle via GET /v1/receipt/{receipt_id} (authorized -> settled + batch_tx). Classic responses have NO settlement field. classic every request settles on-chain before the response returns; receipts carry tx_hash as always. Keys minted BEFORE 2026-07-19 default to classic — migrate with PATCH {"settlement_mode": "v2"}. Payment source — "pay_with": "credits" | "usdc" | "auto" (null clears -> default) credits spend the email account's card-purchased credits (spend-only) usdc spend the wallet's on-chain USDC vault auto credits first, then USDC (requires linked accounts, below) Explicit credits/usdc is STRICT: if that balance cannot cover the request you get 402 — no silent fallback. Per-request override (beats the key setting): X-Pay-With: credits | usdc | auto curl -X PATCH https://gateway.charis.im/v1/auth/keys/pim_sk_a1b2c3 \ -H "Authorization: Bearer $KEY" \ -d '{"settlement_mode": "v2", "pay_with": "auto"}' Account linking (enables cross-custody pay_with; Bearer = the CLOUD key): POST /v1/cloud/link_wallet { "wallet_key": "pim_sk_..." } -> { linked, wallet } (wallet_key = a wallet-bound API key — proves control of the wallet) GET /v1/cloud/link -> { wallet | null } DELETE /v1/cloud/link_wallet -> unlink Two funding flows, one invariant: - Card -> credits: deposit dollars by card at charis.im (email account). Credits are SPEND-ONLY — they never withdraw and never convert to crypto. - USDC on Base -> vault: self-custody, withdrawable any time (charis.im -> Vault). ## Endpoint reference Inference: POST /v1/chat/completions OpenAI-compatible (streaming, tools, json) POST /v1/messages Anthropic-compatible (Claude Code; same money path, Charis model namespace) POST /v1/messages/count_tokens input-token ESTIMATE for a Messages body Account & money (the ones integrators ask for): GET /v1/balances?address=0x..&chain=base vault (spendable) + wallet USDC balances GET /v1/usage/{address} per-model/provider usage + spend rollup GET /v1/points/{address} $CHARIS balance + today's mining GET /v1/receipt/{receipt_id} per-call audit record (tokens, price, provider, attestation, on-chain tx hash) (There is NO /v1/balance, /v1/credits, or /v1/me — those 404 with a pointer here.) Market & platform data (public, no auth): GET /v1/pricing pricing sheet, one row per live model/lane: price_in/price_out (market-floor ask, $/1M), all_in_in/all_in_out (what a request is actually charged, fees included), source_count, tok_s, discount_pct (vs the retail ceiling), history (14-day best-price series, 1h buckets, downsampled). Markdown: /pricing.md; HTML: https://charis.im/pricing GET /v1/models[?supports=..][&include_dead=1] catalog + prices + capabilities + per-leg house/quant + retail_ref (above) GET /v1/quote?model=..&lane=..&n_in=&n_out= live all-in quote for a token budget GET /v1/price_history?grade=... sampled best-price history (charts) GET /v1/orderbook?model=... per-lane asks/bids + capacity GET /v1/trades?model=... recent fills (trade tape) GET /v1/stats platform aggregates (30s cache) GET /v1/metrics?days=90 daily gauge snapshots (trend charts) GET /v1/savings savings vs retail since the cost-plus epoch (`since` field; the app's discount tiles use the same scope) + `all_time` = charged $ and request count ONLY, counted since `data_since` (2026-07-09, or earlier where pre-restart real-money receipts were backfilled) — pre-epoch retail/"saved" are never fabricated GET /v1/savings/{address} one wallet's savings vs retail GET /v1/points/season $CHARIS season pulse GET /v1/chain configured settlement chains + vault addresses GET /v1/attestation/report?grade=..:tee&source=venice enclave attestation + pubkey ## Auction your Venice DIEM (nightly-expiring allowance -> USDC) Staking VVV on Venice grants a daily DIEM inference allowance that expires unused at 00:00 UTC. Charis auctions that capacity into USDC WITHOUT holding your Venice key — you self-host a tiny proxy that holds it; Charis only gets the proxy URL, and on the encrypted (:tee) lane relays SEALED CIPHERTEXT only (the key and buyer plaintext never leave your infra + Venice's enclave). Setup: 1. Stake VVV until the dashboard shows a daily DIEM allowance, then mint a Venice key that is Inference-Only with a Daily DIEM Limit (the hard cap — it can spend only free DIEM, never paid USD). PITFALL (hit live): below the 0.1 DIEM staking minimum the key reports accessPermitted:false and balances DIEM:0 EVEN THOUGH the dashboard shows staked DIEM — the key looks healthy but every request fails and /health reads 0. Stake past 0.1 DIEM before debugging anything else. 2. Self-host the proxy (~50 lines, no deps — full source + steps at https://charis.im/tools/venice-diem-proxy.txt , raw source at https://charis.im/tools/venice-diem-proxy.js): curl -O https://charis.im/tools/venice-diem-proxy.js VENICE_KEY=vk_... PORT=8790 node venice-diem-proxy.js It exposes /v1/chat/completions (key injected) + GET /health {diem_remaining, models}. 3. Encrypted-lane listing is OPERATOR-APPROVED: lane "tee" registrations are gated by an allowlist (an un-approved id gets 403). Contact the operator (support@charis.im) with your supplier id to be whitelisted. 4. Confirm the proxy is reachable: POST /v1/suppliers/diem/health {"base_url":"https://your-proxy"} -> {diem_remaining, models} 5. Register (one row per model; payout_addr REQUIRED, EIP-55 checksummed — that is where your USDC lands): POST /v1/suppliers {"id","model","lane":"tee","kind":"venice-diem", "base_url":"https://your-proxy","daily_diem_usd":1, "reset_utc_hour":0,"payout_addr":"0x…"} Pricing = a continuous daily auction, computed at quote time (you never post prices): opens ~10% under Venice retail at 00:00 UTC; marks down inventory-aware through the day (sold-so-far vs the linear sell-through pace — behind pace discounts harder, so camping midnight risks stockout); floor = 2% of retail (never a below-cost dump); the last 5% of the day clears any remainder at the floor; resets at midnight UTC. Your leg lists as an anonymous charis-<6hex> supplier. Earnings accrue per fill minus the 2% passthrough fee (GET /v1/suppliers/{id}/earnings?token=...). At >= $5 accrued the sweep pays payout_addr in a batched USDC transfer from the revenue Safe, signed by the operator, and appends an immutable payout record (amount, address, tx hash). Exhausted DIEM -> the proxy reports 0 -> delisted until the next reset. Full guide: charis.im/docs#doc-supply-diem. ## Supplier earnings & payouts GET /v1/suppliers?model=... live suppliers for a model: asks, health, accrued earnings GET /v1/suppliers/{id}/earnings?token=... self-scoped (token = the delete_token from registration, or the admin token): accrued USDC per chain, accrued_total_usd, min_payout_usd, payout_addr, and the immutable payout history (amount, address, tx hash per payout) Payouts: register with a payout_addr (USDC on Base, EIP-55 checksummed) to be payable. At >= $5 accrued (min_payout_usd) the periodic sweep pays a batched USDC transfer from the revenue Safe, decrements the ledger by exactly the paid amount, and appends the payout record the earnings endpoint returns. ## Errors Every response carries an `X-Request-Id` header; every error body is JSON `{"detail": ..., "request_id": ...}` — quote the request_id when reporting issues (it is also echoed in `x_charis.request_id` on successful calls). 400 bad request / chain not configured / capability unsupported ("no source for supports ; see /v1/models?supports=...") / unsupported tool type (hosted tools — see Knowledge plane above) — nothing charged 401 unknown or revoked API key / bad signature 402 vault below minimum (mint) | vault balance too low (chat) | key credit limit reached 403 key disabled or expired 409 no supplier under your X-Limit-All-In ceiling (nothing charged — raise or retry) 429 rate limited (per wallet/IP) OR settlement_capacity_reached — the network's hourly on-chain settlement cap is reached; NOTHING is charged, retry after `retry_after_s` (also in the Retry-After header). A transient capacity limit, not an error in your call. 503 suppliers temporarily unavailable; NOTHING is charged. Comes with a Retry-After header (retail sources cool <=60s, so availability returns fast). Price-truth: we never silently upsell. ## Notes for agents - Vault balance = spendable money; wallet USDC is NOT spendable until deposited (charis.im -> Vault). $1 covers thousands of small completions. - Aborting/disconnecting a streamed request does NOT stop the generation: it runs to completion server-side and is billed in full. Cap your exposure with max_tokens (enforced upstream; the worst case is already reserved at admission). - Prices float with the market; read x_charis.cost_usd per call for exact spend. - Every settled call mines $CHARIS for the payer (fee-weighted, fixed daily pool).