API reference
Base URL https://api.spin.inductionlabs.com/v1. Hosted Spin is https://api.spin.inductionlabs.com/v1. The machine-readable spec is at openapi.json.
Authentication#
Every request carries a bearer token. The token’s prefix decides what it can do.
| Scope | Token | Where it lives |
|---|---|---|
| Account key | cnv_live_…, created in the dashboard | Host or CI |
| Signed-in session | Supabase access token from the dashboard | Browser |
| Wallet token | cnv_agent_…, returned when a wallet is created | That agent’s sandbox |
curl "https://api.spin.inductionlabs.com/v1/account" -H "Authorization: Bearer $SPIN_API_KEY"
Conventions#
Account amounts are integer USD cents. Agent-side amounts are exact decimal USD strings. Lists return the latest 100 rows. Paid operations require a stable Idempotency-Key header: retry with the same key and body, and never retry an ambiguous paid operation under a new key.
Errors share one shape and a stable code. 402 insufficient funds, 403 wrong token scope, 409 conflicting retry or pending cleanup, 503 retryable dependency failure.
| Field | Type | Notes |
|---|---|---|
error | object | Fields: code (string); message (string). |
request_id | string |
Account endpoints#
Called from your host or CI with an account key, or by the dashboard with a signed-in session. They allocate and settle wallets and never expose resource credentials except the wallet token itself.
GET/v1/account#
Account credit balance in USD cents
Response · Account
| Field | Type | Notes |
|---|---|---|
available_cents | integer | |
reserved_cents | integer | |
spent_cents | integer | |
welcome_credit_cents | integer |
GET/v1/keys#
List account key metadata, never secrets
Response
JSON object.
POST/v1/keys#
Create an account key (Verified Supabase session or self-host operator only)
Request body
| Field | Type | Notes |
|---|---|---|
name required | string | 1–80 characters. |
Response
JSON object.
DELETE/v1/keys/{id}#
Revoke an account API key
Response
JSON object.
GET/v1/wallets#
List latest 100 owned wallets
Response
JSON object.
POST/v1/wallets#
Reserve credit and mint sandbox credentials
Request body · WalletRequest
| Field | Type | Notes |
|---|---|---|
name required | string | 1–80 characters. |
budget_cents required | integer | 100–1000000. |
expires_in_seconds | integer | Seconds until the wallet auto-closes (60..2592000). 0 means the wallet never expires; only closing settles it. 0–2592000. default 86400. |
Response · Allocation
| Field | Type | Notes |
|---|---|---|
id | string | |
status | string | |
agent_token | string | Sensitive wallet credential. Never log it. |
env | object | Fields: SPIN_BROKER_URL (string (uri)); SPIN_BROKER_TOKEN (string): Same scoped credential as agent_token; inject only into its sandbox. |
GET/v1/wallets/{id}#
Read an owned wallet, including original budget and used budget after closure
Response
JSON object.
GET/v1/resources#
List resource summaries across owned wallets, newest first; no resource credentials
Query parameters
| Name | Type | Notes |
|---|---|---|
wallet_id | string | Filter to an owned wallet, including closed wallets. 0–200 characters. |
before | string | The previous response next_cursor; pages contain up to 100 resources. Empty next_cursor means the last page. 0–200 characters. |
Response
JSON object.
GET/v1/wallets/{id}/token#
Retrieve the existing token for an owned, active wallet; closed or expired wallets return 409
Response · WalletToken
| Field | Type | Notes |
|---|---|---|
agent_token required | string | Existing scoped credential for an active, unexpired wallet. Never log it. |
POST/v1/wallets/{id}/close#
Freeze spending, clean up resources, settle, return unused credits
Response
JSON object.
GET/v1/wallets/{id}/resources#
List resources belonging to an owned wallet
Response
JSON object.
POST/v1/billing/checkout#
Create a Stripe Checkout session (browser session required)
Request body
| Field | Type | Notes |
|---|---|---|
amount_cents required | integer | 500–100000. |
Response
JSON object.
Agent endpoints#
Called from inside a sandbox with that wallet’s token. Spending is capped by the wallet budget and expiry; paid operations reserve their maximum price first and settle afterwards.
GET/v1/budget#
Inspect wallet spending (decimal USD strings)
Response
JSON object.
GET/v1/compute/offers#
List approved Modal, SF Compute and Vast.ai offers
Response
JSON object.
GET/v1/compute/list#
List wallet-owned resources
Response
JSON object.
GET/v1/models#
List metered OpenRouter models
Response
JSON object.
POST/v1/compute/create#
Prepay a fixed-duration compute lease; no early-release refund
Request body · ComputeRequest
| Field | Type | Notes |
|---|---|---|
provider required | "modal" | "sfcompute" | "vastai" | |
spec required | object | Provider-specific spec; GET /v1/compute/offers first. Modal: gpu,gpus,cpus,memory_gib,max_seconds. SF: chip,max_seconds. Vast: offer_id,disk_gb,max_seconds; stop destroys the rental and disk. |
Response · ComputeResource
| Field | Type | Notes |
|---|---|---|
id | string | Spin resource ID for subsequent requests. |
provider_id | string | null | |
provider | string | |
status | string | |
billing_state | "reserved" | "uncertain" | "settled" | Payment state; settled resources may still be running and need cleanup. |
billing_policy | "prepaid_time" | |
charged_usd | string | Cumulative prepaid charge, including confirmed extensions; early release does not refund it. |
reserved_usd | string | Unresolved creation hold; zero after capture. Check /v1/budget for pending extension holds. |
expires_at | number | Current deadline, Unix seconds. |
purchased_seconds | integer | |
price_hourly_usd | string | |
supports_extension | boolean |
POST/v1/compute/extend#
Prepay additional seconds on an active SF or Vast lease; Modal returns extension_not_supported
Request body
| Field | Type | Notes |
|---|---|---|
id required | string | |
additional_seconds required | integer | Added to the current expiry; constrained by wallet expiry, remaining credit, and provider limits. 1–86400. |
Response · ComputeResource
| Field | Type | Notes |
|---|---|---|
id | string | Spin resource ID for subsequent requests. |
provider_id | string | null | |
provider | string | |
status | string | |
billing_state | "reserved" | "uncertain" | "settled" | Payment state; settled resources may still be running and need cleanup. |
billing_policy | "prepaid_time" | |
charged_usd | string | Cumulative prepaid charge, including confirmed extensions; early release does not refund it. |
reserved_usd | string | Unresolved creation hold; zero after capture. Check /v1/budget for pending extension holds. |
expires_at | number | Current deadline, Unix seconds. |
purchased_seconds | integer | |
price_hourly_usd | string | |
supports_extension | boolean |
POST/v1/run/close#
Freeze wallet spending and clean up agent resources
Response
JSON object.
POST/v1/compute/stop#
Stop an owned resource
Request body · ResourceID
| Field | Type | Notes |
|---|---|---|
id required | string |
Response
JSON object.
POST/v1/compute/ssh#
Ssh an owned resource
Request body · ResourceID
| Field | Type | Notes |
|---|---|---|
id required | string |
Response
JSON object.
POST/v1/compute/status#
Status an owned resource
Request body · ResourceID
| Field | Type | Notes |
|---|---|---|
id required | string |
Response
JSON object.
POST/v1/compute/exec#
Execute inside an owned remote sandbox
Request body
| Field | Type | Notes |
|---|---|---|
id required | string | |
command required | string | |
timeout | integer | 1–3600. default 300. |
Response
JSON object.
POST/v1/compute/upload#
Upload at most 8 MiB to an owned resource
Request body
| Field | Type | Notes |
|---|---|---|
id required | string | |
path required | string | |
content_base64 required | string |
Response
JSON object.
POST/v1/transfer/sign#
Sign an exact object URL when transfer storage is configured
Request body
| Field | Type | Notes |
|---|---|---|
object required | string | |
operation required | "get" | "put" | |
expires_seconds | integer | default 900. |
Response
JSON object.
POST/v1/openrouter/key#
Get wallet-scoped key and OpenAI-compatible base_url
Request body
Free-form JSON object.
Response
JSON object.
POST/v1/chat/completions#
Metered non-streaming OpenRouter request
Request body
| Field | Type | Notes |
|---|---|---|
model required | string | |
messages required | object[] | |
max_tokens | integer | min 1. |
stream | false |
Response
JSON object.