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.

ScopeTokenWhere it lives
Account keycnv_live_…, created in the dashboardHost or CI
Signed-in sessionSupabase access token from the dashboardBrowser
Wallet tokencnv_agent_…, returned when a wallet is createdThat 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.

FieldTypeNotes
errorobjectFields: code (string); message (string).
request_idstring

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

Auth: Account key · Returns 200

Response · Account

FieldTypeNotes
available_centsinteger
reserved_centsinteger
spent_centsinteger
welcome_credit_centsinteger

GET/v1/keys#

List account key metadata, never secrets

Auth: Account key · Returns 200

Response

JSON object.

POST/v1/keys#

Create an account key (Verified Supabase session or self-host operator only)

Auth: Signed-in session · Returns 201

Request body

FieldTypeNotes
name requiredstring1–80 characters.

Response

JSON object.

DELETE/v1/keys/{id}#

Revoke an account API key

Auth: Signed-in session · id in the path · Returns 200

Response

JSON object.

GET/v1/wallets#

List latest 100 owned wallets

Auth: Account key · Returns 200

Response

JSON object.

POST/v1/wallets#

Reserve credit and mint sandbox credentials

Auth: Account key · Idempotency-Key header required · Returns 201

Request body · WalletRequest

FieldTypeNotes
name requiredstring1–80 characters.
budget_cents requiredinteger100–1000000.
expires_in_secondsintegerSeconds until the wallet auto-closes (60..2592000). 0 means the wallet never expires; only closing settles it. 0–2592000. default 86400.

Response · Allocation

FieldTypeNotes
idstring
statusstring
agent_tokenstringSensitive wallet credential. Never log it.
envobjectFields: 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

Auth: Account key · id in the path · Returns 200

Response

JSON object.

GET/v1/resources#

List resource summaries across owned wallets, newest first; no resource credentials

Auth: Account key · Returns 200

Query parameters

NameTypeNotes
wallet_idstringFilter to an owned wallet, including closed wallets. 0–200 characters.
beforestringThe 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

Auth: Account key · id in the path · Returns 200

Response · WalletToken

FieldTypeNotes
agent_token requiredstringExisting scoped credential for an active, unexpired wallet. Never log it.

POST/v1/wallets/{id}/close#

Freeze spending, clean up resources, settle, return unused credits

Auth: Account key · id in the path · Returns 200

Response

JSON object.

GET/v1/wallets/{id}/resources#

List resources belonging to an owned wallet

Auth: Account key · id in the path · Returns 200

Response

JSON object.

POST/v1/billing/checkout#

Create a Stripe Checkout session (browser session required)

Auth: Signed-in session · Idempotency-Key header required · Returns 200

Request body

FieldTypeNotes
amount_cents requiredinteger500–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)

Auth: Wallet token · Returns 200

Response

JSON object.

GET/v1/compute/offers#

List approved Modal, SF Compute and Vast.ai offers

Auth: Wallet token · Returns 200

Response

JSON object.

GET/v1/compute/list#

List wallet-owned resources

Auth: Wallet token · Returns 200

Response

JSON object.

GET/v1/models#

List metered OpenRouter models

Auth: Wallet token · Returns 200

Response

JSON object.

POST/v1/compute/create#

Prepay a fixed-duration compute lease; no early-release refund

Auth: Wallet token · Idempotency-Key header required · Returns 200

Request body · ComputeRequest

FieldTypeNotes
provider required"modal" | "sfcompute" | "vastai"
spec requiredobjectProvider-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

FieldTypeNotes
idstringSpin resource ID for subsequent requests.
provider_idstring | null
providerstring
statusstring
billing_state"reserved" | "uncertain" | "settled"Payment state; settled resources may still be running and need cleanup.
billing_policy"prepaid_time"
charged_usdstringCumulative prepaid charge, including confirmed extensions; early release does not refund it.
reserved_usdstringUnresolved creation hold; zero after capture. Check /v1/budget for pending extension holds.
expires_atnumberCurrent deadline, Unix seconds.
purchased_secondsinteger
price_hourly_usdstring
supports_extensionboolean

POST/v1/compute/extend#

Prepay additional seconds on an active SF or Vast lease; Modal returns extension_not_supported

Auth: Wallet token · Idempotency-Key header required · Returns 200

Request body

FieldTypeNotes
id requiredstring
additional_seconds requiredintegerAdded to the current expiry; constrained by wallet expiry, remaining credit, and provider limits. 1–86400.

Response · ComputeResource

FieldTypeNotes
idstringSpin resource ID for subsequent requests.
provider_idstring | null
providerstring
statusstring
billing_state"reserved" | "uncertain" | "settled"Payment state; settled resources may still be running and need cleanup.
billing_policy"prepaid_time"
charged_usdstringCumulative prepaid charge, including confirmed extensions; early release does not refund it.
reserved_usdstringUnresolved creation hold; zero after capture. Check /v1/budget for pending extension holds.
expires_atnumberCurrent deadline, Unix seconds.
purchased_secondsinteger
price_hourly_usdstring
supports_extensionboolean

POST/v1/run/close#

Freeze wallet spending and clean up agent resources

Auth: Wallet token · Returns 200

Response

JSON object.

POST/v1/compute/stop#

Stop an owned resource

Auth: Wallet token · Returns 200

Request body · ResourceID

FieldTypeNotes
id requiredstring

Response

JSON object.

POST/v1/compute/ssh#

Ssh an owned resource

Auth: Wallet token · Returns 200

Request body · ResourceID

FieldTypeNotes
id requiredstring

Response

JSON object.

POST/v1/compute/status#

Status an owned resource

Auth: Wallet token · Returns 200

Request body · ResourceID

FieldTypeNotes
id requiredstring

Response

JSON object.

POST/v1/compute/exec#

Execute inside an owned remote sandbox

Auth: Wallet token · Returns 200

Request body

FieldTypeNotes
id requiredstring
command requiredstring
timeoutinteger1–3600. default 300.

Response

JSON object.

POST/v1/compute/upload#

Upload at most 8 MiB to an owned resource

Auth: Wallet token · Returns 200

Request body

FieldTypeNotes
id requiredstring
path requiredstring
content_base64 requiredstring

Response

JSON object.

POST/v1/transfer/sign#

Sign an exact object URL when transfer storage is configured

Auth: Wallet token · Returns 200

Request body

FieldTypeNotes
object requiredstring
operation required"get" | "put"
expires_secondsintegerdefault 900.

Response

JSON object.

POST/v1/openrouter/key#

Get wallet-scoped key and OpenAI-compatible base_url

Auth: Wallet token · Returns 200

Request body

Free-form JSON object.

Response

JSON object.

POST/v1/chat/completions#

Metered non-streaming OpenRouter request

Auth: Wallet token · Idempotency-Key header required · Returns 200

Request body

FieldTypeNotes
model requiredstring
messages requiredobject[]
max_tokensintegermin 1.
streamfalse

Response

JSON object.