WaitSpin public contract

WaitSpin API And Agent Docs

This is the public contract for implemented WaitSpin routes, Stripe Checkout and crypto MPP block purchases, verified user earning surfaces, and guarded publisher money surfaces.

Last updated: July 2, 2026. The public launch surface is CLI, REST API, Stripe Checkout and Stripe/Tempo stablecoin MPP block purchases, verified user earning surfaces for VS Code, Cursor Editor Mode, Devin Desktop, Claude Code, Antigravity CLI, GitHub Copilot CLI, MiMo Code, OpenCode, Grok Code CLI, and Qoder CLI, public market, and guarded wallet/ledger/Connect/payout routes.

Base URLs And Auth

Public API host: https://api.waitspin.com. API discovery is available at https://api.waitspin.com/v1. Public launch host: https://waitspin.com. Authenticated routes use Authorization: Bearer wts_live_.... Campaign creation also requires an Idempotency-Key UUID.

The canonical REST API contract is OpenAPI-backed: /openapi/waitspin-api.openapi.json. Code routes and public docs must stay in parity with that spec.

Agent markdown is available at https://waitspin.com/.well-known/agents.md and https://waitspin.com/waitspin/agents.md. It is scoped to the shipped route allowlist, including Stripe Checkout, crypto MPP block purchases, verified user surfaces, and guarded publisher money surfaces.

Public client source and trust-boundary docs are published at github.com/citedy/waitspin and summarized at /waitspin/trust.

VS Code Marketplace provenance is published as JSON at /provenance/waitspin-vscode.json. It records the public source repository, extension version, release source commit, Marketplace URL, npm package version, VSIX filename, and VSIX SHA256 without committing the VSIX binary. The live Marketplace status is published at /status/waitspin-vscode-marketplace.json.

Authenticated routes are API-key rate limited at 60/minand count against monthly API-call quota except extension serve/impression polling. GET /v1/market is IP limited at 60/min.

Use control keys for advertiser, Connect, and payout commands. Extension keys created with the publisher-extension profile are scoped to user install registration, serve polling, impression events, and read-only wallet visibility.

User Wallet And Payouts

A connected VS Code user install means WaitSpin can serve sponsored cards and report wallet visibility. Payout readiness is a separate money state: earnings must mature, the available balance must reach the minimum payout, and a Stripe Express payout account must be set up before withdrawals.

Fresh impression earnings first appear as maturing. They are recorded in the ledger, but they are not withdrawable until the public maturity window completes. Matured earnings move into available. Payouts are eligible only when the available balance reaches the minimum payout threshold shown by the wallet policy.

Payout account not set up refers to Stripe Express payout onboarding, not the VS Code plugin connection. The extension uses an extension API key for read-only wallet visibility and sponsor polling; payout account setup uses guarded Connect/payment routes and a control key.

Primary setup path: open Set up payout account, verify your WaitSpin account email, choose the payout country, and continue to Stripe Express. The browser flow creates the Stripe onboarding link server-side; it does not expose a reusable control API key to the page.

WaitSpin creates Stripe Express payout accounts with the transfers capability. Users receive payouts from WaitSpin; they must not be enabled to accept customer payments through the WaitSpin platform account. WaitSpin does not force a Stripe service-agreement type in API calls; Stripe applies the agreement required for the platform/country pairing.

Direct Stripe payments-balance top-ups can be unavailable when Stripe has not approved the platform profile for that top-up path or when top-ups in the selected currency are unavailable. In that case, live payout smoke can use eligible advertiser Checkout proceeds once the funds are available in Stripe; do not treat a disabled top-up button as a WaitSpin wallet-code failure.

Advanced automation path: run waitspin wallet connect --country US with a control key, then open the Stripe Express onboarding URL it returns. Extension keys cannot create payout accounts because they are intentionally limited to user install registration, serve polling, impression events, and read-only wallet views.

Stable wallet blockers such as earnings_maturing, balance_below_minimum, and connect_account_missing are backend decision codes. User surfaces should translate them into readable payout guidance while keeping the API codes available for diagnostics.

User Levels And Limits

User level controls how much paid inventory one user account or install can receive while the account warms up. A fresh eligible user starts at level 1/10 and can receive paid sponsored cards immediately when eligible campaigns are available. There is no manual trusted/not-trusted switch for normal public VS Code user installs.

Level can rise by 1 after each clean 24h period of billable activity, up to level 10/10. Risk signals such as refund/dispute pressure, invalid impression receipts, velocity limits, or cap pressure can reduce the effective level or pause paid supply for review.

Daily exposure limits are level-based. At level 1, one user account can receive up to 3%of a campaign's effective daily prepaid inventory, and one install can receive up to 0.5% of that campaign per day. At level 10, those caps rise to 30% and 5%. A separate separate global daily user revenue cap also scales with level.

204 No Content or a VS Code "no eligible sponsor" state does not mean the plugin is disconnected. It can mean there are no active eligible campaigns, self-owned campaigns were excluded, inventory is exhausted, or this install reached its daily exposure limit for the currently available campaigns.

Implemented Routes And Guarded Surfaces

MethodPathAuthPurpose
GET/v1noneRead API discovery metadata with docs and OpenAPI URLs.
POST/v1/keys/requestnoneRequest an email verification code.
POST/v1/keys/verifynoneVerify code and receive a wts_live_ key.
POST/v1/list/subscribenoneRequest double opt-in publisher or founding advertiser email updates.
GET/v1/marketnoneRead active public campaign leaderboard.
POST/v1/campaignscampaigns:writeCreate a draft campaign and pending block purchase. Requires Idempotency-Key.
GET/v1/campaignscampaigns:readList campaigns for the API key account.
POST/v1/blocks/checkoutblocks:purchaseCreate or reuse a Stripe Checkout URL for a pending campaign.
POST/v1/blocks/mpp-cryptoblocks:purchase or verified MPP credentialCreate or reuse a Stripe/Tempo stablecoin MPP payment challenge for a pending block purchase.
POST/v1/publishers/registerpublishers:writeRegister a user install ID for a supported earning surface.
POST/v1/serve/nextserve:readReturn the next sponsored message, 204 for genuine empty inventory, or 503 with Retry-After when capacity is temporarily exhausted.
POST/v1/events/impressionevents:writeRecord a billable impression after the server-side visible interval.
GET/v1/wallet/statuswallet:readRead user balance, payout eligibility, and Connect status.
POST/v1/wallet/connectconnect:manageCreate or refresh a Stripe Express onboarding link.
GET/v1/wallet/ledgerwallet:readRead user delivery, refund-reversal, and dispute-hold delivery-ledger rows.
POST/v1/wallet/payoutsconnect:managePreview or execute a guarded idempotent user payout.
POST/api/waitspin/webhookStripe signatureStripe Checkout activation, refund/dispute accounting, and Connect account sync.

Request And Response Shapes

GET /v1
response: { "name": "WaitSpin REST API", "version": "v1", "api_base_url": "https://api.waitspin.com/v1", "docs_url": "https://waitspin.com/docs", "openapi_url": "https://waitspin.com/openapi/waitspin-api.openapi.json", "routes": { "discovery": ["/v1"], "control": ["/v1/market"], "webhooks": ["/api/waitspin/webhook"] } }

POST /v1/keys/request
request:  { "email": "[email protected]", "intended_use": "optional" }
response: { "ok": true, "expires_in_seconds": 900, "delivery": "email" }

POST /v1/keys/verify
request:  { "email": "[email protected]", "code": "123456" }
control key response: { "account_id": "wacc_...", "api_key": "wts_live_...", "scopes": ["campaigns:write","campaigns:read","blocks:purchase","serve:read","events:write","wallet:read","connect:manage","analytics:read","publishers:write"], "trust_level": "email_verified" }
extension key response: { "account_id": "wacc_...", "api_key": "wts_live_...", "scopes": ["publishers:write","serve:read","events:write","wallet:read"], "trust_level": "email_verified" }

POST /v1/list/subscribe
request:  { "email": "[email protected]", "segment": "publisher" | "advertiser", "source": "landing_hero", "turnstileToken": "optional", "company": "" }
response: { "ok": true, "already_subscribed": false, "expires_in_seconds": 86400, "delivery": "email" }
note: /list/confirm and /list/unsubscribe are email/browser-link routes, not API-key routes.

GET /v1/market
response: { "campaigns": [{ "campaign_id": "wcamp_...", "ad_line": "...", "brand_name": null, "bid_cpm_micros": 1000000, "impressions_served": 0, "status": "active" }] }

POST /v1/campaigns
headers:  Authorization: Bearer wts_live_...; Idempotency-Key: <v4-uuid>
request:  { "ad_line": "...", "destination_url": "https://example.com", "brand_name": "Example", "price_per_block_cents": 500, "blocks": 1 }
response: { "campaign_id": "wcamp_...", "block_purchase_id": "wbp_...", "status": "draft", "blocks": 1, "price_per_block_cents": 500 }

GET /v1/campaigns
response: { "campaigns": [{ "id": "wcamp_...", "ad_line": "...", "status": "draft", "blocks_purchased": 1, "units_remaining": 1000000 }] }

POST /v1/blocks/checkout
request:  { "campaign_id": "wcamp_..." }
response: { "checkout_url": "https://checkout.stripe.com/...", "block_purchase_id": "wbp_..." }

POST /v1/blocks/mpp-crypto
request:  { "campaign_id": "wcamp_...", "block_purchase_id": "wblk_..." }
unpaid:   402 Payment Required with WWW-Authenticate: Payment ... method="tempo"
pending:  { "ok": false, "status": "payment_pending", "block_purchase_id": "wblk_...", "stripe_payment_intent_id": "pi_...", "stripe_status": "processing" }
response: { "ok": true, "status": "activated", "block_purchase_id": "wblk_...", "campaign_id": "wcamp_...", "stripe_payment_intent_id": "pi_...", "activated_blocks": 1, "payment_receipt": "..." }
retry:    { "ok": true, "status": "activated", "block_purchase_id": "wblk_...", "campaign_id": "wcamp_...", "stripe_payment_intent_id": "pi_...", "activated_blocks": 1, "payment_receipt": null, "idempotent": true }

POST /v1/publishers/register
request:  { "install_id": "wins_...", "target": "status-bar-fallback" | "claude-code" | "antigravity" | "copilot" | "mimocode" | "opencode" | "grok" | "qoder" }
response: { "publisher_id": "wpub_...", "install_id": "wins_...", "target": "status-bar-fallback" | "claude-code" | "antigravity" | "copilot" | "mimocode" | "opencode" | "grok" | "qoder" }

POST /v1/serve/next
request:  { "install_id": "wins_...", "slot_id": "optional" }
empty:    204 No Content only after an admitted inventory lookup finds no eligible campaign
overload: 503 Service Unavailable with Retry-After: 2; clients must retain their current state and retry
response: { "serve_id": "wss_...", "creative": { "line": "...", "destination_url": "https://example.com", "campaign_id": "wcamp_..." }, "min_visible_ms": 5000, "expires_at": "...", "serve_receipt": "wtsr_v1..." }

POST /v1/events/impression
request:  { "serve_id": "wss_...", "serve_receipt": "wtsr_v1...", "install_id": "wins_...", "visible_ms": 5000 }
response: { "ok": true, "billed_micro_units": 5000 }

GET /v1/wallet/status
response: { "account_id": "wacc_...", "balance": { "available_micro_units": 0, "maturing_micro_units": 0, "held_micro_units": 0, "reversed_micro_units": 0, "reversal_debt_micro_units": 0, "paid_micro_units": 0, "lifetime_earned_micro_units": 0, "pending_payout_micro_units": 0 }, "connect": { "connected": false, "stripe_account_id": null, "payouts_enabled": false, "details_submitted": false }, "payout_policy": { "min_payout_cents": 1000, "cadence_days": 7, "currency": "eur", "earning_maturity_hours": 72, "eligible": false, "transfer_cents": 0, "next_eligible_at": null, "blocked_reasons": ["connect_account_missing"] }, "payout_hold": { "active": false, "reason": null, "created_at": null }, "publisher_trust": { "level": 1, "base_level": 1, "max_level": 10, "status": "warming", "clean_days": 0, "normal_cap_share_bps": 1000, "first_billable_at": null, "next_level_at": null, "reasons": [], "paid_supply_allowed": true, "paid_supply_blocked_reasons": [] } }

POST /v1/wallet/connect
response: { "stripe_account_id": "acct_...", "onboarding_url": "https://connect.stripe.com/...", "payouts_enabled": false, "details_submitted": false }

GET /v1/wallet/ledger?limit=50
response: { "entries": [{ "id": "wled_...", "event_type": "impression", "block_purchase_id": "wbp_...", "source_ledger_id": null, "stripe_event_id": null, "gross_micro_units": 5000, "publisher_micro_units": 3000, "platform_micro_units": 2000, "earning_matures_at": "...", "earning_matured_at": null, "created_at": "..." }] }

POST /v1/wallet/payouts
headers:  Authorization: Bearer wts_live_...; Idempotency-Key required for non-dry-run
request:  { "dry_run": true, "confirm_test_transfer": false }
response:
{
  "ok": true,
  "dry_run": true,
  "amount_micro_units": 0,
  "amount_cents": 0,
  "currency": "eur",
  "eligible": false,
  "blocked_reasons": ["connect_account_missing", "earnings_maturing", "balance_below_minimum"],
  "min_payout_cents": 1000,
  "cadence_days": 7,
  "next_eligible_at": null,
  "payouts_enabled": false,
  "details_submitted": false
}

POST /api/waitspin/webhook
auth:     Stripe signature over raw body
events:   checkout.session.completed, checkout.session.async_payment_succeeded, charge.refunded, charge.dispute.created, account.updated
response: { "ok": true }

POST /v1/campaigns idempotency keys expire after 24 hours. Reusing a key with the same payload replays the stored response; reusing it with a different payload returns a conflict response (409).

Public user earning targets are status-bar-fallback, installed from VS Code Marketplace in VS Code with code --install-extension waitspin.waitspin-vscode or in Cursor Editor Mode with cursor --install-extension waitspin.waitspin-vscode --force, or from Open VSX in Devin Desktop with devin-desktop --install-extension waitspin.waitspin-vscode --force, while the WaitSpin lifecycle command auto-detects %LOCALAPPDATA%\devin\bin\devin.exe on Windows, then connected inside that editor with WaitSpin: Connect and earn, claude-code, installed by waitspin claude-code install --compose-existing, antigravity, installed by waitspin antigravity install --compose-existing, copilot, installed by waitspin copilot install --compose-existing, mimocode, installed by waitspin mimocode install, and opencode, installed by waitspin opencode install, and grok, installed by waitspin grok install, and qoder, installed by waitspin qoder install. Claude Code, Antigravity CLI, and GitHub Copilot CLI support use first-class statusLine.command paths; MiMo Code uses a managed shell hook; OpenCode uses a managed TUI plugin entry; Grok Code CLI uses a managed text-asset footer patch with hash-backed restore; Qoder CLI uses the official UserPromptSubmit hook with statusMessage/systemMessage plus the official Stop hook for the later visibility callback. Cursor Editor Mode, Devin Desktop, and Cline VS Code extension installs are covered by the WaitSpin VS Code-compatible extension; Devin uses the Open VSX listing while standalone Cline CLI remains outside the public install contract. Other native spinner patch targets remain deferred until official statusline/plugin support exists.

Cursor and Devin Desktop are also first-class local CLI lifecycle targets: waitspin extension install --target cursor, waitspin extension status --target cursor, waitspin extension uninstall --target cursor, waitspin extension install --target devin, waitspin extension status --target devin, and waitspin extension uninstall --target devin. These local labels still map to status-bar-fallback; they are not API targets.

Legal And Payment Disclosures

Review the Terms and Privacy notices before install, Checkout, or user install registration. Unused prepaid block handling is support-reviewed; no automated account-credit balance, redemption flow, or self-serve cash refund request flow is shipped.

VS Code, Cursor, And Devin User Setup

The first-class VS Code, Cursor Editor Mode, and Devin Desktop user path is the WaitSpin VS Code Marketplace extension. Install it in VS Code with code --install-extension waitspin.waitspin-vscode or in Cursor with cursor --install-extension waitspin.waitspin-vscode --force, or install the same extension ID from Open VSX in Devin Desktop with devin-desktop --install-extension waitspin.waitspin-vscode --force, then run WaitSpin: Connect and earn inside the matching editor.

Latest VS Code Marketplace extension for VS Code and Cursor: v0.1.16.

Latest Open VSX extension for Devin Desktop: v0.1.16. State: published. Status artifact: /status/waitspin-vscode-open-vsx.json.

The extension requests or accepts an extension API key, registers the VS Code-compatible install through POST /v1/publishers/register, stores the key in VS Code SecretStorage, and starts wallet/sponsor polling against https://api.waitspin.com. Cursor and Devin Desktop use the same SecretStorage-backed extension path; there is no separate Cursor or Devin API target or WaitSpin package.

CLI setup remains an advanced local lifecycle path: waitspin extension install --target vscode, waitspin extension install --target cursor, or waitspin extension install --target devin. Cursor and Devin installation does not put an API key in editor argv or settings; finish activation with WaitSpin: Connect and earn.

Product / Agent Quick Start

Python/Go agents should call npx --yes waitspin ... --json and parse stdout; no native SDK is required.

# Credential-free agent demo path.
npm view waitspin version
export WAITSPIN_API_KEY=wts_demo_agent_quickstart
npx --yes waitspin market --demo --json
npx --yes waitspin bid create --demo --line "Your ad" --url https://example.com --price-per-block 500 --blocks 1 --json
npx --yes waitspin bid checkout demo_campaign_001 --demo --json
npx --yes waitspin status --all --demo --json
# Done: every command returns ok=true, mode=demo, and stable demo IDs.

# Authenticated advertiser/publisher path.
npx skills add citedy/waitspin
npm view waitspin version
npx --yes waitspin init --email [email protected] --key-profile control
read -rs WAITSPIN_API_KEY && export WAITSPIN_API_KEY && printf '
'
waitspin bid create --line "Your ad" --url https://example.com --price-per-block 500 --blocks 1
waitspin bid checkout CAMPAIGN_ID
npx --yes waitspin init --email [email protected] --key-profile publisher-extension

# Advanced agent install for detected supported targets
waitspin install --all --dry-run --compose-existing
waitspin install --all --compose-existing
waitspin status --all

# VS Code user extension
# Marketplace: https://marketplace.visualstudio.com/items?itemName=waitspin.waitspin-vscode
code --install-extension waitspin.waitspin-vscode

# Cursor Editor Mode user extension
cursor --install-extension waitspin.waitspin-vscode --force
waitspin extension install --target cursor
waitspin extension status --target cursor
waitspin extension uninstall --target cursor

# Devin Desktop user extension
# Open VSX: https://open-vsx.org/extension/waitspin/waitspin-vscode
devin-desktop --install-extension waitspin.waitspin-vscode --force
waitspin extension install --target devin
waitspin extension status --target devin
waitspin extension uninstall --target devin
# Then run "WaitSpin: Connect and earn" in the matching editor.

# VS Code CLI fallback:
waitspin extension install --target vscode
waitspin extension status --target vscode

# Claude Code statusline
waitspin claude-code install --compose-existing
waitspin claude-code status

# Antigravity CLI statusline
waitspin antigravity install --compose-existing
waitspin antigravity status

# GitHub Copilot CLI statusline
waitspin copilot install --compose-existing
waitspin copilot status

# MiMo Code shell hook
waitspin mimocode install
waitspin mimocode status

# OpenCode TUI plugin slot
waitspin opencode install
waitspin opencode status

# Grok Code CLI footer
waitspin grok install
waitspin grok status

# Qoder CLI UserPromptSubmit/Stop hooks
waitspin qoder install
waitspin qoder status

The credential-free path is complete when waitspin market --demo --json returns ok=true, mode=demo, and stable demo IDs without creating an account, campaign, Stripe Checkout, install, or billable impression. The authenticated path is complete when the CLI returns the created campaign or install ID, and the matching waitspin bids list or waitspin status --all command can read that state back.

Explicit target commands remain the canonical debug path. waitspin install --all is an advanced agent command that installs only detected supported targets and reports structuredinstalled, would_install, skipped_not_detected, skipped_conflict, and failed_rollback arrays. Use an extension API key for polling/events. The VS Code extension can connect a user install inside VS Code and stores keys in SecretStorage; the Claude Code, Antigravity CLI, and GitHub Copilot CLI installers store managed runtime state under ~/.waitspin, preserve existing status lines with --compose-existing, and do not patch native binaries. Qoder CLI stores a managed hook runtime under ~/.waitspin and configures Qoder's official UserPromptSubmit/Stop hooks without patching native binaries.

Extension keys created with the publisher-extension profile are valid only for user install registration, serve polling, impression events, and read-only wallet status/ledger. They cannot create campaigns, start Checkout, manage Connect, or execute payouts.

Response And Error Contract

Successful JSON responses include route-specific fields and no cache storage for authenticated control routes. Empty inventory returns 204 No Content only after an admitted inventory lookup finds no eligible campaign. Temporary serve-capacity exhaustion returns 503 Service Unavailable with Retry-After: 2; clients must retain their current state and retry instead of treating overload as empty inventory. Client errors use standard HTTP status codes such as 400, 401, 403, 409, 422, and 429 with a JSON error body; rate limits may include Retry-After.

Billed impression delivery uses a 60% user share and 40% platform share. Stripe processing fees are absorbed from the platform share unless the payment policy changes.

Not In The Public Contract Yet

Native spinner patches beyond supported status surfaces, click billing, account-credit redemption, cash refund self-service, live payout transfers without explicit operator flags and deployed evidence, geo targeting, and house ads are withheld from the public contract until implementation and launch evidence are complete.

Legal And Security

WaitSpin uses hashed API-key storage, host isolation, trusted-edge checks, rate limits, and audit logging. Keep API keys secret and do not commit VS Code, Claude Code, Qoder, or environment files containing credentials.