Seller Endpoints

All seller endpoints require authentication via one of:

  • si_seller_ API key: Authorization: Bearer si_seller_xxx (programmatic access)
  • Privy session cookie (web UI)

A si_seller_ key cannot access buyer endpoints, and vice versa. Session cookies work for both.

Everything you can do in the seller dashboard, you can do here over the API. The dashboard is a pure client of this API, so managing keys, offers, pricing, health, earnings, and payouts are all HTTP calls you can script with no browser. Seller keys support create, list, and revoke; offers have a full lifecycle (create, read, update, deactivate, bulk create, bulk cancel, reset health, rotate credential).

Authentication — SIWE (Sign-In With Ethereum)

Get a seller API key using wallet signature auth. No browser needed.

Get Challenge

GET/v1/seller/auth/challenge

Returns a SIWE message with nonce and 5-minute expiry. The message must be signed and returned within 5 minutes. The server validates domain, uri, chainId, issuedAt (must be within ±5min of server time), and expirationTime (max 10min from issuedAt).

Get challenge
bash
GET /v1/seller/auth/challenge?address=0xYourWallet
json
{
  "message": "www.surplusintelligence.ai wants you to sign in...",
  "nonce": "abc123def456",
  "expires_at": 1713312000000
}

Issue Key

POST/v1/seller/auth/keys

Exchange the signed SIWE message for a seller API key. Save the returned key — it will not be shown again.

Issue key
bash
POST /v1/seller/auth/keys
{
  "message": "<SIWE message from challenge>",
  "signature": "0x...",
  "label": "my-bot",           // optional
  "expires_at": 1713398400000  // optional, ms timestamp
}
json
{
  "key": "si_seller_abc123def456...",
  "id": "01JQ8G7YV3M4N5P6Q7R8S9T0AB",
  "wallet": "0xYourWallet",
  "label": "my-bot",
  "created_at": 1713312000000
}

The secret is the key field, shown once. expires_at is present only if you set one on the request.

Manage Keys

Once you hold one seller key (or a web session), you can mint and manage more programmatically, no browser required. Seller keys support create, list, and revoke. (Rename and per-key routing preferences are buyer-only, since a seller key carries no routing config.)

POST/v1/seller/keys

Create another si_seller_… key (up to 25 active per wallet). Requires an Idempotency-Key header. The secret is in the key field of the response, shown once.

GET/v1/seller/keys

List keys, redacted (id, key_prefix, label, timestamps, revoked_at). An existing seller key or web session authorizes this.

DELETE/v1/seller/keys/{keyId}

Revoke a key. Irreversible; returns 204.

Manage keys
bash
POST /v1/seller/keys
Authorization: Bearer si_seller_xxx
Idempotency-Key: 9f8c1e2b-...     # required
{ "label": "batch-bot" }
# → {
#     "id": "01JQ8G7YV3M4N5P6Q7R8S9T0AB",
#     "key": "si_seller_9f8c1e2b...",   # shown once
#     "key_prefix": "si_seller_9f8c1e2b",
#     "label": "batch-bot",
#     "created_at": 1713312000000
#   }
bash
GET /v1/seller/keys
Authorization: Bearer si_seller_xxx
bash
DELETE /v1/seller/keys/{keyId}
Authorization: Bearer si_seller_xxx

Offers

POST/v1/seller/offers

Create offer.

PATCH/v1/seller/offers/{id}

Update price/caps.

DELETE/v1/seller/offers/{id}

Deactivate / soft-delete (encrypted key is retained; revoke at provider for immediate key invalidation).

GET/v1/seller/offers

List my offers (filter with ?model=, ?status=active|paused|inactive; paginate with max_items / next_token).

GET/v1/seller/offers/{id}

Read one offer (status, health, price, caps).

To deactivate every active offer at once, use POST /v1/seller/offers/bulk-cancel (see Offer operations); otherwise remove them one at a time.

One offer per credential, base URL and model

The same API key can back as many different models as you like — that is the normal case and nothing about it changes. What is refused is a second active offer for the same model on the same key and base URL, whoever owns it: the rule is global across workspaces and organizations.

A conflict returns 409 duplicate_offer_for_credential. When the existing offer is your own, the response carries its offer_id in error.detail so you can update or delete it instead of creating a duplicate; when it belongs to someone else, no identifier is returned.

The same rule applies to anything that brings an offer back into the active set — reactivating a paused offer, or rotating a credential onto a key that already serves that model. Deleting or deactivating an offer frees its slot, so the key can be relisted for that model straight away; if a slot ever appears stuck, the next listing attempt reclaims it automatically.

When the conflicting offer is your own, its id is returned as error.detail.existing_offer_id.

In bulk creation, a conflicting row comes back as status: "error" with duplicate_offer_for_credential in the message; retrying will not change the outcome.

Offers Surplus has delisted

Surplus deactivates an offer itself when its provider endpoint cannot serve the model it is listed for — for example a video model listed against a host that exposes no video API at all. Such an offer can never complete a job: every request routed to it fails after the buyer has been quoted.

A delisted offer stays visible and editable, but setting status: "active" on it returns 400 offer_delisted_by_operator and it is not restored by rotating its credential. It is not a pause you can undo. List the model against a provider that serves it, or contact support if you believe the endpoint does work.

Offers
bash
POST /v1/seller/offers
Authorization: Bearer si_seller_xxx
Idempotency-Key: 9f8c1e2b-...     # required
{
  "model": "claude-opus-4.6",
  "api_key": "your-provider-api-key",
  "seller_base_url": "https://api.venice.ai/api/v1",
  "price_input_per_1m": 12.00,
  "price_output_per_1m": 48.00,
  "cap_daily_usd": 100.00,
  "payout_address": "0xOptional..."
}
bash
PATCH /v1/seller/offers/{id}
Authorization: Bearer si_seller_xxx
{ "price_input_per_1m": 10.00 }
bash
DELETE /v1/seller/offers/{id}
Authorization: Bearer si_seller_xxx
bash
GET /v1/seller/offers
Authorization: Bearer si_seller_xxx
bash
GET /v1/seller/offers/{id}
Authorization: Bearer si_seller_xxx

Bulk Create

POST/v1/seller/offers/bulk

Create multiple offers from discovery results.

Up to 200 offers per request. Bulk-created offers support the same cap_daily_usd field as single-offer creation.

Bulk create
bash
POST /v1/seller/offers/bulk
Authorization: Bearer si_seller_xxx
{
  "api_key": "your-provider-api-key",
  "base_url": "https://api.venice.ai/api/v1",
  "offers": [
    { "model": "claude-opus-4.6", "cost_multiplier": 0.5 },
    { "model": "gpt-5.4", "cost_multiplier": 0.5 }
  ]
}

Offer operations

POST/v1/seller/offers/bulk-cancel

Deactivate ALL of your active offers in one call. Fire-and-forget: returns 202 with an operation_id; poll it for progress.

POST/v1/seller/offers/{id}/reset-health

Clear an offer's failure backoff and return it to rotation immediately.

POST/v1/seller/offers/{id}/rotate-credential

Swap an offer's upstream provider key in place, without recreating it.

GET/v1/seller/operations/{id}

Poll an async operation (such as a bulk cancel) for status, processed, total, and failed_count.

Offer operations
bash
POST /v1/seller/offers/bulk-cancel
Authorization: Bearer si_seller_xxx
# → 202 { "operation_id": "...", "object": "operation", "status": "queued", "total": 12 }
bash
POST /v1/seller/offers/{id}/reset-health
Authorization: Bearer si_seller_xxx
bash
POST /v1/seller/offers/{id}/rotate-credential
Authorization: Bearer si_seller_xxx
{ "api_key": "new-provider-key" }
bash
GET /v1/seller/operations/{id}
Authorization: Bearer si_seller_xxx
# → { "id": "...", "status": "succeeded", "processed": 12, "total": 12, "failed_count": 0 }

Model Discovery

POST/v1/seller/discover

Auto-detect models + pricing from a provider. Requires seller auth; streams NDJSON, one line per model.

Parses Venice, OpenRouter, Bankr, and generic pricing formats automatically.

Discover
bash
POST /v1/seller/discover
Authorization: Bearer si_seller_xxx
{ "api_key": "your-provider-api-key", "base_url": "https://api.venice.ai/api/v1" }

Test Connection

POST/v1/seller/test-connection

Verify endpoint is reachable and returns valid responses.

Test connection
bash
POST /v1/seller/test-connection
Authorization: Bearer si_seller_xxx
{ "api_key": "your-provider-api-key", "base_url": "https://api.venice.ai/api/v1", "model": "claude-opus-4.6" }

Earnings

GET/v1/seller/earnings

Precomputed earnings for a range: total_earned_usdc, pending_usdc, in_flight_usdc, paid_usdc (integer micro-USD strings), plus daily, by_model, share, a recent_sales teaser (up to 20), and any payout_hold.

Query param: range = 7d (default), 30d, 90d, or lifetime. Team orgs can add ?workspace_id=.

Earnings
bash
GET /v1/seller/earnings
Authorization: Bearer si_seller_xxx

Earnings History

GET/v1/seller/earnings-history

Cursor-paginated sale history, deeper than the 20-row earnings teaser. Params: limit (default 25), cursor, workspace_id. Returns recent_sales[] + next_token.

Usage Export

GET/v1/seller/usage/export

Your full sales record as CSV. Redirects (302) to a short-lived presigned download. Params: from, to, limit, format=csv, workspace_id.

Health Log

GET/v1/seller/health-log

Returns recent health events for your offers (failures, backoffs, recoveries).

Query params: offer_id (filter to one offer), since / until (ms epoch), max_items, next_token, sort_order (ASC | DESC).

Health log
bash
GET /v1/seller/health-log
Authorization: Bearer si_seller_xxx

Payouts

GET/v1/seller/payouts

Your payout position: available_usdc, in_flight_usdc, paid_usdc, the threshold_usdc, per-recipient groups, and executed-payout history with Base transaction hashes.

POST/v1/seller/payouts/withdraw

Request a withdrawal of your available balance. In batched mode the scheduled drain settles the pending balance (status: scheduled); self_submit: true instead requests a signed EIP-3009 authorization for you to broadcast yourself (paying your own gas). A withheld balance returns status: held with a reason.

POST/v1/seller/payouts/submitted

Report the on-chain tx_hash for a payout you broadcast yourself.

Payouts
bash
GET /v1/seller/payouts
Authorization: Bearer si_seller_xxx
bash
POST /v1/seller/payouts/withdraw
Authorization: Bearer si_seller_xxx
{ "self_submit": false }
bash
POST /v1/seller/payouts/submitted
Authorization: Bearer si_seller_xxx
{ "payout_id": "...", "tx_hash": "0x..." }

Saved Provider Configs

GET/v1/seller/configs

Reusable saved provider-key cards (base URL + key fingerprint + hint; the key itself is never returned). Cards are created as a side effect of creating an offer, so there is no create endpoint here: management is list + delete. List them, or DELETE /v1/seller/configs/{id} to remove one. Bulk offer creation, discovery, and health probes can reuse a saved key via reuse_key_fingerprint instead of re-sending api_key.

Rate Limits

All seller endpoints are rate-limited per API key and per wallet:

Route ClassPer-Key LimitPer-Wallet Aggregate
Offers CRUD (GET/POST/PATCH/DELETE)30 req/min200 req/min
Bulk-create / Discover5 req/min200 req/min
Earnings / Health (read-only)60 req/min
Key management (issue/revoke)10 req/min per IP
Challenge (SIWE)20 req/min per IP

429 responses include Retry-After (seconds) and X-RateLimit-Reset (Unix timestamp) headers.

These route limits are API-abuse controls. They are not a marketplace-side spend ceiling for provider usage. For provider-quota protection, use cap_daily_usd and provider-side API-key limits where available. There is no per-week or per-month USD cap on marketplace seller offers today.

Key Storage and Deletion

Seller provider API keys are encrypted at rest with AES-256-GCM under AWS KMS envelope encryption: a per-secret data key is requested from KMS, used to encrypt the plaintext, and the ciphertext plus the KMS-encrypted data key are stored in a private S3 bucket. The server decrypts keys only when it needs to call the seller's provider endpoint or run seller-owned management actions such as discovery/test-connection/health reset.

Deleting an offer is currently a soft delete: active = false on the offer record, removed from routing, encrypted API key retained in storage. There is no self-serve hard-delete endpoint yet. Sellers should revoke deleted offer keys at their upstream provider if immediate invalidation is required.

See Security & Privacy for the full current trust model.

Error Responses

All errors follow the format:

json
{
  "error": {
    "message": "Human-readable description",
    "type": "authentication_error | authorization_error | invalid_request_error | rate_limit_error | server_error"
  }
}
StatusMeaning
400Invalid request (bad JSON, missing fields, unsupported model)
401Invalid or missing API key
403Wrong key type (e.g., buyer key on seller endpoint)
404Offer not found or doesn't belong to your wallet
409This model is already listed on this credential and base URL (duplicate_offer_for_credential)
429Rate limit exceeded
500Server error