Supplier machine API
The supplier marketplace machine API: mint an sk-sup-… key in the cabinet, call every endpoint with Authorization: Bearer, onboard connections in batches and read connections, offers, price changes, earnings and statistics.
The supplier cabinet has a machine surface: a supplier can onboard accounts in bulk and read their own connections, offers, prices, price-change history, earnings and statistics from a script, using a supplier-scoped API key instead of a browser session.
This is additive. The browser cabinet is unchanged, and the machine routes assemble the same payloads the cabinet pages do, so the two surfaces cannot report different numbers.
Get a key
Machine keys are minted and revoked in the supplier cabinet. The full token is shown once, at creation, and is never returned again.
- Open the supplier cabinet and find the Machine API keys section.
- Give the key a label (optional) and press create.
- Copy the token immediately. It starts with
sk-sup-. - Store it in a secret manager or an environment variable. Never commit it or send it in a support message.
Cabinet management endpoints
These routes use the cabinet session or tracking token. They deliberately do not accept a machine key, so a leaked machine key cannot mint or revoke other keys.
| Parameter | Type | Description |
|---|---|---|
GET /api/marketplace/suppliers/me/api-keys | list | List keys: id, label, prefix, active, lastUsedAt, createdAt. Never the token or its hash. |
POST /api/marketplace/suppliers/me/api-keys | mint | Body { "label"?: string }. Returns { "key": "sk-sup-…", "apiKey": { … } } — the only time the token appears. |
DELETE /api/marketplace/suppliers/me/api-keys/{id} | revoke | Revoke one key. 404 when the id does not belong to the caller. |
Authentication
Every machine route requires the supplier key as a bearer token:
Authorization: Bearer sk-sup-<64 hex>curl https://getvibeapi.com/api/marketplace/suppliers/api/v1/connections \
-H "Authorization: Bearer $SUPPLIER_API_KEY"A request resolves the key to exactly one supplier, and every query is filtered by that id. Revocation is scoped by both the key id and its owner, so a supplier cannot revoke another supplier’s key. Never log or transmit the key.
401 message, so a caller cannot tell the cases apart. Send the key only over HTTPS.Read endpoints
All are GET, API-key authenticated and scoped to the authenticated supplier. Micro-USD money is returned as strings; token counts are numbers.
Connections
The supplier’s connected accounts, each with what it has earned (payable only).
{
"connections": [
{
"id": "…",
"connectionType": "subscription-api-key",
"subscriptionProductLabel": "…",
"verifiedEndpoint": "https://…",
"status": "ACTIVE",
"enabled": true,
"verificationState": "verified",
"verifiedBy": "…",
"label": "nightly-1",
"account": "…",
"verifiedModels": ["…"],
"servingModels": ["…"],
"lastVerifiedAt": "2026-09-18T09:00:00.000Z",
"proxy": "…",
"proxySource": "pool",
"proxyCountry": "de",
"proxyExpiresAt": "2026-10-18T09:00:00.000Z",
"proxyExpiryDays": 30,
"proxyLost": false,
"probedVia": "…",
"lastError": null,
"needsReauth": false,
"accountLimits": null,
"accountLimitsSource": null,
"accountLimitsCheckedAt": null,
"earnings": { "earnedMicros": "1250000", "requestCount": 42 },
"createdAt": "2026-09-01T09:00:00.000Z"
}
]
}Offers
The supplier’s offers with the current prices and the live percentage of the official card.
{
"offers": [
{
"id": "…",
"baseModel": "claude-opus-5",
"claimedModel": "claude-opus-5",
"providerLabel": "…",
"status": "ACTIVE",
"statusReason": null,
"inputMicrosPer1M": "1000000",
"outputMicrosPer1M": "2000000",
"cacheReadMicrosPer1M": null,
"blendedPctOfOfficial": "0.42",
"officialPrice": null,
"pctOfOfficial": 42,
"trafficTier": "…",
"cooldownUntil": null,
"cooldownReason": null,
"firstVerifiedAt": "2026-09-01T09:00:00.000Z",
"activatedAt": "2026-09-01T09:00:00.000Z",
"lastManualPriceChangeAt": null,
"maxConcurrentRequests": 8,
"servingEnabled": true
}
]
}Price-change history
The price-change ledger for one of the supplier’s own offers: planned, applied and cancelled changes. An offer belonging to another supplier answers 404.
{
"offerId": "…",
"schedules": [
{
"id": "…",
"offerId": "…",
"priceInUsd": "1.000000",
"priceOutUsd": "2.000000",
"effectiveFrom": "2026-10-01T00:00:00.000Z",
"createdAt": "2026-09-18T09:00:00.000Z",
"cancelledAt": null,
"appliedAt": null
}
]
}Statistics
The same shape the cabinet statistics page shows: totals, a 14-day daily series and a per-offer byOffer breakdown.
{
"totals": {
"requests": 12000,
"successes": 11880,
"errors": 90,
"timeouts": 30,
"successRatePct": 99,
"promptTokens": 9000000,
"completionTokens": 4000000,
"cacheTokens": 2500000,
"sourceCostMicros": 12345678,
"avgLatencyMs": 812.4,
"p50LatencyMs": 640,
"p95LatencyMs": 2100
},
"daily": [
{ "date": "2026-09-05", "requests": 800, "successes": 795, "errors": 4, "promptTokens": 600000, "completionTokens": 250000, "sourceCostMicros": 900000 }
],
"byOffer": [
{ "offerId": "…", "baseModel": "claude-opus-5", "requests": 12000, "successRatePct": 99, "promptTokens": 9000000, "completionTokens": 4000000, "tokens": 13000000, "sourceCostMicros": 12345678, "hitRate": 0.28, "effTotal": 3.14, "rank": 1 }
]
}Earnings
Up to 500 recent settlement-eligible route attempts, newest first.
{
"events": [
{
"id": "…",
"baseModel": "claude-opus-5",
"result": "SUCCESS",
"promptTokens": "1200",
"completionTokens": "800",
"paidMicros": "420000",
"settlementEligible": true,
"finishedAt": "2026-09-18T09:00:00.000Z",
"settlementStatus": "pending_or_settled"
}
]
}Create connections
Create one account or a whole pack from a credential you already hold — no browser OAuth round-trip. The body is one item, a { "items": [ … ] } envelope, or a bare array. All three are normalised to the same per-item result. The older /api/marketplace/suppliers/api/v1/connections/batch path is an alias of the same handler.
Credential shapes
Every item names the pool with provider (the readable pool name, e.g. codex or claude-code) and carries exactly one credential shape:
| Parameter | Type | Description |
|---|---|---|
codexAuthJson | codex-bundle | The Codex CLI ~/.codex/auth.json pasted verbatim: auth_mode, tokens.{id_token,access_token,refresh_token,account_id}, _meta.{plan_type,email}. |
accessToken + refreshToken | claude-tokens | The Claude Code access token (sk-ant-oat01-…) and its refresh token. |
refreshToken | antigravity-refresh | An Antigravity refresh token. |
apiKey | api-key | A ready subscription API key. |
bindToken + oauthCode | oauth-code | The OAuth result from the browser flow, when you used it instead of a pasted bundle. |
Every item carries a working egress proxy, and there is no proxy-less connection. Pass your own proxy, or a proxyCountry to prefer one of ours; pass neither and the server assigns and proves one of its own before any vendor request. If no pool proxy can be proved, the item is refused as proxy_unavailable. externalId is echoed back untouched for correlation.
curl https://getvibeapi.com/api/marketplace/suppliers/api/v1/connections \
-H "Authorization: Bearer $SUPPLIER_API_KEY" \
-H "Content-Type: application/json" \
--data @pack.jsonpack.json — one own proxy and one country-pool proxy:
[
{
"externalId": "codex-1",
"provider": "codex",
"connectionLabel": "nightly-codex-1",
"codexAuthJson": "{\"auth_mode\":\"chatgpt\",\"tokens\":{\"id_token\":\"…\",\"access_token\":\"…\",\"refresh_token\":\"…\",\"account_id\":\"…\"},\"_meta\":{\"plan_type\":\"plus\",\"email\":\"…\"}}",
"proxy": { "scheme": "http", "host": "proxy.example.com", "port": 8899, "username": "u", "password": "p" },
"offers": [{ "baseModel": "deepseek-v4.1-flash", "inputMicrosPer1M": 1000000, "outputMicrosPer1M": 2000000 }]
},
{
"externalId": "claude-1",
"provider": "claude-code",
"connectionLabel": "nightly-claude-1",
"accessToken": "sk-ant-oat01-…",
"refreshToken": "sk-ant-ort01-…",
"proxyCountry": "de",
"offers": [{ "baseModel": "claude-opus-5", "inputMicrosPer1M": 1000000, "outputMicrosPer1M": 2000000 }]
}
]duplicate refusal and writes nothing new. Retry a failed pack item by item.Partial success is the contract
Each item is proved against the vendor and written on its own. One bad row never fails the batch, and an item is reported ok: true only after its connection row is committed.
{
"supplierId": "…",
"accepted": 1,
"rejected": 1,
"results": [
{
"index": 0,
"externalId": "row-1",
"ok": true,
"connectionId": "…",
"connectionType": "subscription-api-key",
"verificationState": "verified",
"verifiedModels": ["…"],
"subscriptionProduct": "codebuddy-cn",
"verifiedEndpoint": "https://…",
"latencyMs": 210
},
{
"index": 1,
"externalId": "row-2",
"ok": false,
"code": "bind_session_invalid",
"error": "the authorization session expired or did not match"
}
]
}Per-item refusal codes
| Parameter | Type | Description |
|---|---|---|
invalid_credential | code | The vendor rejected the credential shape or value. |
connection_type_unavailable | code | That connection type is not available yet. |
subscription_product_unavailable | code | The named subscription product is not offered. |
provider_unavailable | code | The selected provider is not available. |
bind_session_invalid | code | The OAuth session expired or did not match. |
proxy_required | code | The resolved proxy had no usable URL. (An item that names no proxy is assigned one instead.) |
proxy_unavailable | code | No pool proxy could be proved for the requested country, or for any country when none was named. |
proxy_race_lost | code | That proxy was taken by another connection just now. |
probe_failed | code | The vendor rejected the credential during the live probe. |
model_not_served | code | The account did not confirm a requested model; the refused models are in the item's "models" array. |
duplicate | code | The same credential fingerprint is already connected. |
encryption_unavailable | code | Credential encryption is not configured on the project. |
internal_error | code | An unexpected server error. Retry the single item later. |
Proxy pre-flight
Prove a proxy before you hand over a credential: give your own proxy or a proxyCountry and the server opens the same tunnel it will use for the connection and reaches a neutral host through it. This is only a pre-flight — the create call runs the identical check and refuses before it exchanges a token or probes the vendor, so a broken proxy never costs a credential.
curl https://getvibeapi.com/api/marketplace/suppliers/proxy-check \
-H "Content-Type: application/json" \
-d '{ "proxy": { "scheme": "http", "host": "proxy.example.com", "port": 8899, "username": "u", "password": "p" } }'{ "ok": true, "outcome": "ok", "latencyMs": 42 }422 with outcome: "proxy_failed"; a country with no free pool proxy answers 409 with proxy_unavailable.Browser OAuth (cabinet)
The cabinet’s own two-step OAuth flow, for when you link an account in a browser instead of holding a bundle:
Body { "connectionType": "claude-code" | "codex-oauth" }. Returns the authorizeUrl to open and the signed bindToken that ties the code to the PKCE session. Nothing is persisted.
Submits the completed grant — the bindToken plus the oauthCode and the same offers / proxy fields as a create item. The proxy is proved before the code is exchanged. These two routes use the cabinet session; a machine key cannot call them (use POST /connections with bindToken + oauthCode instead).
Rate limits
| Parameter | Type | Description |
|---|---|---|
Create connections | limit | At most 25 items per call. 20 calls per hour per supplier, on top of a 120/hour per-IP cap. Each item is an outbound vendor probe. |
Proxy pre-flight | limit | 30 calls per hour per IP. Each call opens one tunnel to a neutral host. |
Read endpoints | limit | 600 requests per hour per IP for the shared machine surface unless a route sets its own cap. |
Cabinet key management | limit | 120/hour per IP to list keys, 20/hour to mint, 60/hour to revoke. |
429 response carries Retry-After in seconds. Back off and retry after that delay; re-submitting the same credential is refused as duplicate by the unique credential fingerprint.Error responses
| Parameter | Type | Description |
|---|---|---|
401 | status | Missing, unknown or revoked API key. One fixed message, so a caller cannot distinguish the cases. |
404 | status | The path does not exist on this project (the marketplace is not enabled here), or the addressed offer belongs to another supplier. |
429 | status | Rate limit. Retry-After is returned by the shared limiter. |
400 | status | Malformed JSON or a batch over the cap. The body names the invalid input. |
A machine-readable description of every endpoint is part of the OpenAPI document at /api/openapi. Point an OpenAPI client generator at it to build a typed client.