# SwarmIO Cloud (ioswarm.io) > A hosted agent harness: deep research as an API. One POST kicks off a swarm > that plans the work, browses, reads the sources, and cross-checks what it > finds, then returns a cited report; you poll or stream for the result. > You do not choose an orchestration framework, a model, or a scraping stack > — that is the part this service hosts. > This file is the agent onboarding doc — it is everything an LLM agent needs > to become an authenticated, self-funding API client with no browser and no > human. Base URL for every path below: https://ioswarm.io Sibling machine interfaces (same API, different envelopes): MCP (Model Context Protocol): POST /mcp — tools: route.classify, runs.launch, runs.get, runs.report, runs.cancel, billing.balance, billing.topup_terms, modes.list. Bearer auth identical to below; initialize, tools/list, modes.list and billing.topup_terms answer with no credentials at all. OpenAPI 3.1 spec: GET /api/v1/openapi.json A2A agent card: GET /.well-known/agent-card.json agents.json flows: GET /.well-known/agents.json x402 payments: POST /api/v1/x402/topup — 402 + signed header, USDC on Base. Both wire versions (v1 X-PAYMENT, v2 PAYMENT-SIGNATURE). See "Topping up" below. ## Quickstart: zero to API client in 5 calls 1. Discover the auth backend (public): GET /api/v1/auth/config → { "supabase_url": "…", "supabase_anon_key": "…" } 2. Create an account directly against Supabase Auth: POST {supabase_url}/auth/v1/signup headers: apikey: {supabase_anon_key} Content-Type: application/json body: { "email": "", "password": "" } → { "access_token": "…", … } Email confirmation is DISABLED on this project — the access_token works immediately. Welcome credits land automatically on your first authenticated call. 3. Register as an agent (stamps the account with an agent id + signup IP for abuse tracking; idempotent — safe to call on every boot): POST /api/v1/agent/register Authorization: Bearer {access_token} → { "agent_id": "agt_…", "already_registered": false, "next": "…" } 4. Mint an API key (the raw key is returned ONCE — store it): POST /api/v1/account/api-keys Authorization: Bearer {access_token} Content-Type: application/json body: { "name": "my-agent" } → { "id": 1, "prefix": "sk_swarm_1a2b3c", "key": "sk_swarm_…" } 5. From here on, authenticate everything with: Authorization: Bearer sk_swarm_… Check your state any time: GET /api/v1/agent/me (Bearer access_token or key) → { "registered": true, "agent_id": "agt_…", "signup_open": true } ## Core API (all under /api/v1) Runs — where the work happens: POST /runs { "mode": "research", "input": "" } input aliases: question | prompt | query | target. Common modes: research, news, product, trips, trainer, finance, astrology, horoscope. Returns the run record incl. its id. POST /route { "prompt": "…" } — classify a freeform request into the right mode before spending a run. Never charged. GET /runs — your runs, newest first. GET /runs/:id — run detail: status, then the report when finished. GET /runs/:id/report — the finished report. GET /runs/:id/events — SSE stream of live progress. POST /runs/:id/cancel — stop a running job. GET /runs/:id/artifacts — everything the run wrote to disk. Astrology mode input contract — the chart is computed server-side from constraint lines in the input (never send positions; they are not trusted): { "mode": "astrology", "input": " Constraints (focus strictly on these): - Reading for: - Birth date: YYYY-MM-DD - Birth time: HH:MM <- optional; requires the Timezone line - Timezone: Europe/London <- IANA name; required when Birth time is set - Birth city: Leeds <- optional, wording only - Latitude: 53.8 <- optional; with Longitude adds rising/midheaven - Longitude: -1.55" } Birth date is the only required line. Without a birth time the Moon and any angles are approximate (noon is assumed and the report says so). A run without a parseable Birth date fails fast, uncharged. Have coordinates but no zone? GET /geo/tz?lat=53.8&lon=-1.55 → { "tz": "Europe/London" } — offline boundary lookup, never charged. Horoscope mode — the sky RIGHT NOW: every planet's live position, the aspects between them, retrogrades, and the Moon's phase, computed server-side: { "mode": "horoscope", "input": "" } No birth lines needed: when the caller's account has birth details saved (POST /astro/birth), they are folded into the run automatically and the report is personal; otherwise it is the collective sky. To read for someone else, compose the same constraint lines as the astrology contract above — a "- Birth date:" line in the input always wins over the saved chart. Chat — conversational interface over your runs and account: POST /chats — open a chat. POST /chats/:id/messages { "content": "…" } — send a turn (SSE reply). GET /chats — your chats. Account: GET /account — balance and plan state. GET /account/transactions — credit ledger. POST /billing/checkout { "credits": 100000 } — returns a Stripe Checkout URL. A human with a browser completes it; agents should top up over the crypto rail below instead. ## Topping up when the welcome credits run out Two machine rails onto the same balance. Both are USDC on Base, both credit at the standard rate card with the volume bonus, and one transfer can never be credited twice on either. ### Rail A — x402 (preferred; zero custom code if your stack speaks it) The x402 protocol, both wire versions. If you have an x402 client, point it at the endpoint and stop reading: POST /api/v1/x402/topup?usd=5 Authorization: Bearer {api_key} By hand, it is two requests: 1. POST with your bearer and NO payment header. → 402, carrying the payment requirements twice over: the v1 document in the JSON body (`accepts[]`), the v2 document base64'd in the PAYMENT-REQUIRED header. Read whichever your client speaks and ignore the other. 2. Sign the EIP-3009 `transferWithAuthorization` those requirements describe — you sign only; you broadcast nothing and pay no gas — and repeat the same request with the signed payload base64'd into X-PAYMENT (v1) or PAYMENT-SIGNATURE (v2). → 200 { "credited": true, "credits": …, "balance": …, "transaction": "0x…" } and the settlement echoed in X-PAYMENT-RESPONSE / PAYMENT-RESPONSE. `?usd=` picks the size (default $5, floor $1, ceiling $1,000). A facilitator verifies the signature and broadcasts the transfer; this service never holds a signing key. GET the same path for the full terms, or GET /api/v1/x402/supported for the schemes and networks it speaks. ### Rail B — send-then-claim (any wallet, no x402 client needed) 1. GET /api/v1/billing/crypto (public) → { "enabled": true, "network": "base", "asset": "USDC", "pay_to": "0x…", "min_usd": 1.0, "credits_per_usd": 1000000, … } 2. Transfer USDC on Base (chain id 8453, contract 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) to the pay_to address from any wallet you control. 3. POST /api/v1/billing/crypto-topup { "tx_hash": "0x…" } with your bearer. The transfer is verified on-chain (receipt status, Transfer event to the pay_to address, 12+ confirmations) and credited at the standard rate card, volume bonus included. Idempotent per transaction hash — re-POSTing the same hash answers { "duplicate": true } and never double-credits. If the terms endpoint says "enabled": false, the operator has not published a receiving address yet — both machine rails are then off, and the fallback is handing the Stripe checkout URL to a human. Human path: POST /api/v1/billing/checkout and hand the Stripe URL to your operator. ## Fair use - One account per agent. Register once; reuse the agent_id. - sk_swarm_ keys are hash-stored and unrecoverable — keep yours secret; mint a replacement instead of asking for recovery. - Registration is deliberately open. The trail (agent id + signup IP) exists so abuse can be attributed and stopped: abusive accounts are disabled and their subnets blocked, and the open door closes for everyone. Don't be the reason.