Skip to main content
ReferenceChangelog

Changelog

Notable Releases

October 2026

  • Provider cache hit rate. Each provider row of GET /api/analytics/performance/{model} adds cache_hit_rate_pct: the share of that provider's input tokens that were prompt-cache reads over the trailing 24 hours, to one decimal. It is null until the whole window is measured, and a 0 is a real measurement. The Current offers tables on the Markets page and the model page add a sortable Cache column with the same figure, one value per provider, with offers that have no figure sorting last; image, video and audio models have no Cache column. See Provider Performance.
  • Secret masking (beta). An optional extra layer of protection for text prompts: API keys, private keys, seed phrases, passwords and tokens in a request are replaced with placeholders before it is routed, and every value is put back in the response, wherever the model writes it - passwords, seed phrases, links and markup included - except in the model's reasoning, which comes back as written. It is off by default and opt-in at every layer: send X-SI-Mask-Secrets: on, or set mask_secrets: true on a key (PUT /v1/buyer/keys/{id}/preferences, POST /v1/keys, PATCH /v1/keys/{id}) or on the account (PUT /v1/buyer/preferences), or use the switches on Buy › Routing and on key create and edit. A key cannot opt out of an account that masks. A write that turns it on returns secret_masking_effective_at (about 31 seconds later); the preference responses add secret_masking_available. Masked responses carry x-si-secret-masking, x-si-secrets-masked and, when placeholders were left in place, x-si-secrets-withheld (: si-secrets-withheld <n> on a stream). It covers chat completions, completions, Responses, the Anthropic skin and Decisions; a masked key or account still works on embeddings and media, which are sent as they are, without masking (x-si-secret-masking: not_applied). While masking is running, a request that asks for it and cannot get it is refused, never sent unmasked, with one of nine codes; while it is paused on the service, such a request is sent without masking. Restoring every value trusts the seller's model with where it goes - into commands, links and images too - so read what that means first: Secret masking.
  • Preference writes merge instead of replacing (behaviour change). PUT /v1/buyer/preferences and PUT /v1/buyer/keys/{id}/preferences now merge preferences into what is stored: a field you leave out is unchanged and null clears that one field. Before, the body replaced every stored preference, so { "preferences": {} } cleared them all; it now changes nothing. On PATCH /v1/keys/{id}, provider_trust: "default" clears only the trust setting. A write that loses a race three times answers 409 concurrent_update, which is safe to retry. See Buyer Endpoints.
  • OpenAI's Decisions API on Surplus. POST /v1/decisions now speaks OpenAI's public Decisions API: point the OpenAI SDK at Surplus and call client.decisions.create with gpt-6-luna - input (text or user messages with base64 images) and an ordered array of predicate, choice and score questions, answered in order with per-question refusals. The JEV format (state + a map of questions) keeps working unchanged; a JEV request sent to an OpenAI-format model is translated only when nothing is lost (else 400 decisions_mapping_lossy). Billed on input tokens only, at $0.10 per 1M. Idempotency-Key is no longer used on /v1/decisions (as on the other inference endpoints), and the endpoint documents its 200 status. See Decisions.
  • The signed-in app moved to /app (URL change). Every page that used to live at /buy, /sell, /settings, /finops, /keys, /invite, /imagegen and /payments is now under /app - /buy/keys is /app/buy/keys, /finops/requests is /app/activity/requests, /finops/savings is /app/buy/savings, /finops/webhooks is /app/settings/webhooks, and /settings/budgets is /app/buy/budgets. Old links keep working: every one of them redirects, so bookmarks, emails and integrations do not break. Three things worth knowing: the redirect is temporary (307) while the change settles, so do not cache it; /miniapp now goes to the home page, because the Farcaster Mini App is retired; and /imagegen goes to the dashboard home rather than to an image page, because there is no image surface in the new app yet. The API is unaffected - /api/* and /x402/* are unchanged, and no request path, header or payload moves.
  • Notification links point at the page that fixes the problem. A low-balance or auto-top-up email now opens Funding directly, a dead seller key or unhealthy offer opens your Offers table rather than Earnings, and "Manage alerts" always opens Settings › Notifications. Stripe now returns you to Funding by default instead of the old buy page.

September 2026

  • Privacy: identities no longer cross sides (breaking). The public order book (GET /api/markets/:model) drops offers[].seller, a seller's wallet; seller earnings and /v1/seller/earnings-history drop recent_sales[].buyer, a buyer's wallet; the usage leaderboard's entries drop handle and label (a rank is the entry's position, and each entry carries only spend_micro); and Explorer group_by=provider / filter_provider now use the provider route, never a seller's wallet. Buyers never see a seller's identity and sellers never see a buyer's.
  • Buyer overview and spend analytics. New GET /v1/buyer/overview?range=7d|30d|90d returns spend, savings, the 7-day pace (avg_daily_spend_micro; an account younger than a week is paced over its complete days so far), daily figures and top models in one call, and says coverage: false rather than showing zeros when the figures aren't available. /v1/analytics/spend/* and /v1/analytics/savings/summary accept workspace_id=<id> or all (all needs audit access on a team org) and add cache_read_tokens, cache_write_tokens, cache_hit_requests, fiat_spend_micro, usdc_spend_micro, prompt_cache_savings_micro, prompt_cache_hit_pct, cache_routed_pct, distinct_models, distinct_keys, distinct_providers and cache_routing_mode; top model and key lists go up to 50, and provider groups are provider routes. cache_hit_pct stays as a deprecated alias of the cache-routing figure.
  • Prompt-cache savings, and one rule for "saved". /v1/buyer/savings adds cache_saved_usdc and cache_saved_since: what prompt caching saved you, measured from the date shown. Everywhere a saving is shown it is signed per request and floored at zero on the total, so a request that cost more than the reference is counted honestly.
  • Explorer, request log and sales history. The Explorer adds comma-list filters, a day grouping, order_by/order_dir, settlement, funding and cache filters, the cache and funding-split measures and prompt_cache_hit_pct; sellers can query their sales with side=seller. GET /v1/requests accepts from/to, comma-list filters, statuses, member_id, funding, endpoint, cache and latency_*, and each row carries its settlement status; the request detail adds cache tokens, the fiat/USDC split and whether the seller is trusted. /v1/seller/earnings-history accepts from, to, model, offer_id (up to 10 offers, merged newest first) and settlement (pending, owed, paid, or confirmed for every settled sale; the seller Explorer accepts confirmed too). A filtered read that stops at its read budget says so with truncated, and filtered log reads have their own rate limit (60 per minute; an audit read filtered by workspace_id counts as filtered).
  • Audit. New GET /v1/audit/{entryId}; entries carry a hashed session_ref; the list filters on outcome (now including error), workspace_id and session_ref; a day-only to covers the whole day; and denied actions are recorded.
  • Payments and budgets. GET /v1/payments/balance adds prefunded_usdc_enabled, withdrawals_paused and pending_deposits[] (amount, observed time, live confirmations, status and sending address), and deposit_min_confirmations whenever a deposit is pending. GET /v1/payments/auto-topup adds the card on file (card_brand, card_last4, card_exp_month, card_exp_year). POST /v1/payments/topup and POST /v1/payments/billing-portal accept return_to (legacy, dashboard or a path under /app; anything else is 400 invalid_return_to). New GET /v1/payments/history, your funding history with cursor paging, is rolling out per environment. New GET /v1/budgets/spend returns every visible budget's current spend in one call (a member confined to one workspace also sees the organization budget and their own member budget), and budget spend adds scope_type, scope_id, status and hard_stop. A paused budget now keeps counting spend: it never blocks a request or sends an alert while paused, but its spend is current when you resume it.
  • Seller earnings go deeper. /v1/seller/earnings adds computed_at, range=today|mtd, share.prior (the previous equal period), a token-weighted avg_discount_given_pct, cache_savings_usdc, cache and input/output tokens, model_count, offer_count, top_models_by_earnings, and by_offer / by_provider for today, 7 days, 30 days and month to date. Recent sales add discount_pct, provider (omitted when it wasn't recorded), sold_at and confirmed_at.
  • Seller offers and payouts. Offers add discount_pct, provider, route_kind (direct, aggregator or cloud), credential_kind, credential_prefix, and the last error (last_error_category, last_status_code, last_error_at) with last_probe_at and unhealthy_since; health-test lines add status_code and error_category. New POST /v1/seller/offers/bulk-update runs one action over a selection (offer_ids and/or provider): cancel (a selection is required), reset_health (a media offer still awaiting or failing its render check is not re-admitted), and set_discount bulk repricing, rolling out behind a flag, which reprices discount-priced text and media offers at exactly the prices setting that discount on one offer gives (offers you priced per token, and text models without a current reference price, are counted in failed_count). A provider selection matches offers the way your provider cards name them; if the provider list can't be loaded the operation changes nothing, retries, and otherwise ends failed with error_type: ProviderIdentitiesUnavailable. Pausing, resuming, or editing an offer's daily cap, payout address or key no longer changes how a discount-priced text offer is labelled (pricing_reference), so its displayed discount stays put; only a price edit marks it as priced per token. POST /v1/seller/offers/bulk-cancel still cancels every active offer and now refuses a body naming offer_ids or provider (400 selector_not_supported). Every bulk operation, Cancel all included, fills total once it starts running. GET /v1/seller/payouts adds mode, the payout mode that actually applies to you, and history rows add type, method and paid_at; the withdraw response's mode now reports that same effective mode (batched wherever batched payouts are on).
  • Where your offers stand. Seller offers (GET /v1/seller/offers and /v1/seller/offers/{id}) add best_discount_pct, gap_pct, is_best, outbid, requests_24h, requests_7d, earned_7d_usdc and last_sale_at, and the list adds summary, counted over all your offers whatever the page: active, healthy, failing, auth-error and at-best offers, outbid offers, models and providers live, the last 24 hours' requests and earnings, per-provider and per-model rows, and checked_at. Anything not yet known is null.
  • Markets. GET /api/markets rows and the order book's depth add best_live_discount_pct, median_live_discount_pct and best_trusted_offer_discount_pct, measured against each model's reference price (omitted or null when there is none, never 0). The order book adds realized_7d (the average discount buyers actually got over 7 days, with the week before), discount_trend_7d (the best realized discount per 12 hours), recent_fills[].route and recent_fills_as_of. /api/marketplace adds requests_24h, seller_earned_24h_micro, sellers_live, trusted_sellers_live, providers_live, seller best-discount histograms and computed_at. Provider performance adds p95_latency_ms, and public usage adds discount_7d, discount_histogram_7d and uncached input tokens (input_tokens is gross).
  • Keys, routes and your account. API keys' last_used_at is kept current (seller and admin keys to within five minutes), and your own provider routes (/v1/buyer/providers) add last_used_at and uses_this_week (a route added from now on is counted from creation and reads 0 until first used). Session org entries add role, and the session and POST /v1/auth/active-org add platform_admin. Notification rules add state and last_triggered_at. New GET /v1/orgs/:orgId/invites and DELETE /v1/orgs/:orgId/invites/:inviteId list and revoke pending invites.
  • Keys mean your API keys. Key counts and key breakdowns in spend analytics (distinct_keys, dim=key), the Explorer's key grouping, filter and CSV export, and the request log's key_id (rows, GET /v1/requests/facets and the filter) now count and list only your account's own API keys. Requests from a signed-in session or paid with x402/MPP still count in every total but show no key (key_id: null); filtering by anything other than one of your keys returns 400 (invalid_key_id on the request log, invalid_explorer_query on the Explorer). The request.failed webhook's key_id is the API key the request used, or null.
  • Providers onboarded through configuration show as themselves. A provider added without a code release (for example FusionCode or BytePlus) now appears under its own provider id, never unknown, on seller earnings (by_provider, recent sales), seller offers (provider, summary.providers), the marketplace's providers_live, spend analytics provider groups, the Explorer's provider route, and each market's per-provider breakdown on /api/markets and /api/marketplace. GET /api/providers entries add provider_id, the id every one of those surfaces uses for the provider (for example google for both Google entries), so a client can turn any of them into a display name.
  • Notifications. The low-balance alert rule reads "Notify me when my balance drops below the amount I set.", and notification emails meet WCAG AA contrast and fit a phone's width.
  • Browser errors and CORS. New POST /v1/client-errors and POST /v1/client-errors/csp take scrubbed, rate-limited error and CSP reports from our web apps. Credentialed cross-origin calls are now allowed only from the Surplus dashboard's own origins; any other origin gets Access-Control-Allow-Origin: * without credentials. API-key calls are unaffected.
  • The four CSV exports are deprecated. They now send Deprecation and Sunset headers and stop working on 2027-06-30; the dashboard's Activity pages and the JSON endpoints above replace them.
  • Restricted keys stay restricted. An API key with a scope, trusted-only provider setting, budget, or expiry, and every team-workspace key, can no longer create, rename, configure, or revoke keys, or change account routing preferences or BYOK providers, on /v1/buyer/* and /v1/seller/keys. It gets 403 restricted_key, can still read keys, and can revoke itself. Before this, such a key could mint an unrestricted sibling. Unrestricted keys and dashboard sessions work as before. See Buyer Endpoints.
  • Async media stops failing you quietly. A video or music job whose only sellers are listed against a host that cannot run that endpoint now returns 404 no_sellers_for_model up front, instead of being accepted, charged and failed later; those offers are being removed from the marketplace. Both async endpoints now document their full error table, including which failures are retryable. See Video Generations and Music Generations.
  • One prepaid balance, reported the same everywhere. GET /v1/buyer/me reports credit_balance_usdc as your whole spendable prepaid balance - fiat credit plus deposited USDC - the same figure /v1/payments/balance already returned. Deposited USDC can also fund video and music jobs, which previously saw only fiat credit and could refuse a funded buyer.
  • OpenAI-backed image edits work. Edits routed to OpenAI were rejected by the provider because we sent the wrong field shape. POST /v1/images/edits now works on those models; nothing about the request you send changed. See Image Generations → Image-to-Image.
  • Unknown image sizes no longer error internally. An unrecognized size used to be able to fail the price estimate outright; it is now handled cleanly.
  • Latency and uptime on the offers table: expanding a model on the Markets page now shows each offer's provider latency (median time to first token) and uptime over the trailing 24 hours, both sortable. - means not enough data. See Provider Performance.
  • Model latency, uptime, cache hit rate and throughput on the catalog list: every row of GET /api/markets now carries ttft_p50_ms, the model's median time to first token over the trailing 24 hours across all of its providers (with ttft_samples beside it), uptime_pct over the same window, cache_hit_rate_pct, the share of input tokens served from the prompt cache, and tps_p50, the median output tokens per second while streaming. The per-provider performance endpoint gains tps_p50 and tps_samples too. null means not enough data to measure, never zero. See Market Data.
  • Per-request cost on your API key: buyer API keys can read the exact settled cost of their own requests, and /v1/chat/completions returns a settlement request id so a streamed request's final cost is retrievable once the stream ends.
  • Public provider performance and benchmarked discounts: each model's marketplace page shows measured per-provider performance, and every displayed discount is benchmarked against live upstream list prices. See Provider Performance.
  • Routing controls: pin a request to a chosen set of providers, and set a per-key minimum-discount floor (replacing the older price ceiling) so a key only routes to offers at or above the saving you require. See Routing Controls and Minimum-Discount Routing.
  • Cache and reasoning in usage analytics: usage now breaks out cached input tokens and reasoning tokens per request, so token mix and cost are visible at a glance. See Reasoning Controls.
  • Streaming keep-alive: long generations stay connected while a provider is briefly silent, so a slow but valid response no longer times out mid-stream.
  • Marketplace filters: the marketplace has buyer-facing filters and a dedicated mobile filter view for narrowing the model list without leaving the page.

August 2026

  • Reasoning delivered and billed correctly, end to end: reasoning content is relayed across both the OpenAI and Anthropic wires and in both directions, reasoning tokens are persisted for billing and analytics, and Gemini reasoning survives multi-turn tool calls. See Reasoning Controls.
  • No charge for undelivered output: in-band provider stream faults are detected mid-stream, so a request that delivers zero output tokens is not billed.
  • Organizations: roles and permissions are explained in-product, with documentation for members, workspaces, wallets, API keys, and organization analytics. See Organizations.
  • API-first key management: buyer and seller keys can be created, rotated, and managed entirely over the API. See Buyer Endpoints and Seller Endpoints.
  • One active offer per credential, base URL, and model: duplicate seller offers are now rejected across every workspace, so the same credential cannot be listed against the same model twice.

July 2026

  • Image-to-image editing: new POST /v1/images/edits endpoint: edit an existing image with a text prompt using an edit-capable model (e.g. nano-banana-2-edit, qwen-image-2-edit). POST /v1/images/generations runs the same edit flow when the body carries a source image (image_url, input_images, or a data: URI). Masked/inpaint edits are not supported yet. See Image Generations → Image-to-Image.
  • Image-to-video: POST /v1/video/generations accepts a start frame (image_url), a transition end_image_url, and multi-image input_images (roles start/end/reference). Image-to-video-only models require a source image. See Video Generations → Image-to-Video.
  • Multi-image inputs: both surfaces accept up to 8 source images via input_images (strings or { url, role } objects). On images, the first entry is the base and the rest are reference/edit layers; on video, untagged entries are positional ([start] or [start, end]) and further images take explicit roles. Sources are https:// URLs or data: URIs; URLs are passed to the provider as-is and never fetched by the marketplace.

June 2026

  • Multi-provider pinning: the provider hint on /v1/chat/completions now accepts an array (an allow-list), e.g. "provider": ["zai", "openrouter"], routing to the cheapest healthy offer among the listed providers. A single string still works unchanged. Unrecognized providers are dropped; if every pinned provider is unrecognized the request returns 400 unsupported_provider (a supported provider with no offer for the model still returns 404 no_sellers_for_model). See Chat Completions → Provider Pinning.
  • API moved to AWS: the API base host is now https://api.surplusintelligence.ai. Inference endpoints live under /v1/* (e.g. /v1/chat/completions, /v1/models, /v1/prices), markets under /api/markets, and account endpoints under /v1/buyer/* and /v1/seller/*. Update your base_url to https://api.surplusintelligence.ai/v1. The legacy dedicated fallback-key endpoint was retired; register a catch-all provider with model: null via POST /v1/buyer/providers instead.

April 2026

  • Bankr LLM Gateway: 32 models at 0.05x cost, self-sustaining auto top-up loop (PRs #55-57)
  • External wallet auth fix: Rainbow, WalletConnect now create inference sessions (PR #60)
  • GLM 5.1 added to model registry (PR #59)
  • $500/day giveaway launched

March-April 2026

  • Model naming unification: single source of truth introduced with 58 models / 8 providers at the time (PRs #19-26). Current live counts are higher; use /v1/models and /api/markets for current numbers.
  • Tool format normalization: Cursor full compatibility (PRs #29, 31)
  • x402 payment rail: agents can pay per-request
  • Priority Provider (BYOK): use your own key + marketplace overflow
  • CSV usage export for buyers
  • Payout address feature for sellers

February-March 2026

  • Settlement contract deployed on Base
  • 187 tests (unit + integration)
  • 10 provider integrations with automatic pricing discovery
  • Multi-key support: up to 25 keys per wallet