# 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 ## Free, right now, no key and no account GET /api/v1/pulse → { as_of, prices: [{symbol, price, change_pct, currency, source, as_of}], news: { digest: {title, as_of, report_url, stories:[{headline, link, source, excerpt}]}, headlines: [{headline, link, publisher, published, summary}] }, next: {…} } Live prices for BTC-USD, ETH-USD, SOL-USD, AMZN and NVDA with the session's change (refreshed every minute), today's stories from the platform's newest published news digest with a link to the full cited report, and the market wire's latest headlines (refreshed every ten minutes). The same document is the MCP tool `market.pulse`. Fixed symbol list; custom symbols and full reports are the paid surfaces below. Sibling machine interfaces (same API, different envelopes): MCP (Model Context Protocol): POST /mcp — tools: market.pulse (free), web.search (free: LIVE web search, 25 queries/day per caller, then the $0.02 x402 paid leg over HTTP — the response carries the path), news.brief (paid pointer: today's cited market brief, 5¢ over x402 — the tool returns the exact payment steps; the free raw data is market.pulse), search + fetch (free: the swarm's published, cited reports — the research-connector contract ChatGPT and Claude look for), route.classify, runs.launch, runs.wait, runs.get, runs.report, runs.cancel, billing.balance, billing.topup_terms, modes.list. Bearer auth identical to below; initialize, tools/list, market.pulse, search, fetch, 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 discovery: GET /.well-known/x402.json — every paid endpoint with its price in one document (primitives + run catalogue + payTo). The same document answers the v2 SDK manifest path, /.well-known/x402. 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. ## Have a wallet? Pay per run — no account, no signup, two requests If your stack speaks x402 (Coinbase AgentKit, x402-fetch, the Payments MCP, any client that handles a 402), the product itself is the resource: GET /api/v1/x402/run every mode with its price (free) POST /api/v1/x402/run/research body: { "input": "" } → 402 with the payment requirements (v1 in the body, v2 base64'd in the PAYMENT-REQUIRED header). Sign the EIP-3009 authorization — you broadcast nothing and pay no gas — and repeat the POST with the payload base64'd into X-PAYMENT (v1) or PAYMENT-SIGNATURE (v2). → 202 { "run_id", "status": "running", "poll_url", "report_url", "credentials": { "authorization": "Bearer sk_swarm_…", … }, "paid": { "usd", "credits", "transaction" } } The paying wallet IS the account (`wallet:
`): it is created on the first settled payment (funded by that payment — no starter grant), stamped as an agent, and the `credentials` block carries an sk_swarm_ key — shown once — that reads this run and any future one. Send it as `Authorization: Bearer …` on poll_url and report_url. Pay the same way again for the next run, or launch with the key against the balance (POST /api/v1/runs below). An existing key sent with the payment puts the run on that account instead. Prices are per run at the standard rate card (research ≈ $0.84, news ≈ $0.60, seo ≈ $1.12 — the list is live at /api/v1/x402/run) and a run lands in 3–10 minutes. Modes: research, news, finance, product, seo, trips, trainer, meals, wellness, mentor, money, policy, supply, grants, quote, astrology, horoscope. ### Two cents, no account: the primitives Same x402 leg, same response — the answer arrives in the paid response and nothing is created: POST /api/v1/search { "query": "…" } 25 free/day per caller (≤5 results) → { query, elapsed_ms, providers_used, results: [{url, title, snippet, published}], quota } One URL, two legs: no payment header while the daily quota lasts (the response carries free_remaining_today); over it the 402 carries the requirements — sign and re-send with the payment header for ≈ $0.02 and up to 20 results, no daily cap. POST /api/v1/search/report { "query": "…" } ≈ $0.05, pay-only → { query, report, results, usage, paid } One call: live search (≤10 results) synthesized into a cited bullet brief — every finding ends with its source URL. The report on top of the search rail. POST /api/v1/market/price { "symbol": "AAPL", "interval": "1d" } 10 free/day per caller → { symbol, exchange, interval, known, price, change_pct_1/5/20, range_high/low, indicators: {sma_20, rsi_14, macd, atr_14, vwma_20, trend, …}, quota | paid } Live TradingView price + technical indicators for one asset (optionally EXCHANGE:TICKER). An asset TV cannot resolve is answered FREE — {"known": false}, never charged, never burns quota (resolved before payment is ever requested). Over the free quota ≈ $0.02 per call, settled in USDC. POST /api/v1/video/transcript { "video_id": "dQw4w9WgXcQ" } 10 free/day per caller → { video_id, url, title, duration_sec, lang, segment_count, segments: [{start, end, text}], text, backend, quota | paid } YouTube captions for one video ({"url": "…"} also accepted — watch, shorts, youtu.be, live, embed; optional "lang" default en, "format": segments|text). Served by the searchio transcript service when available (the media sidecar is the fallback — "backend" says which answered); "title" is null when the serving backend has none. An empty transcript panel is capped and "truncated" flags the cut. A video with no usable caption track (private, deleted, captions off, wrong lang) is answered FREE — {"known": false}, never charged, never burns quota (resolved before payment is ever requested). Over the free quota ≈ $0.02 per call, settled in USDC. POST /api/v1/video/search { "query": "rust async runtime" } 25 free/day per caller → { query, count, results: [{id, title, url, channel, duration_sec, view_count, live_status}], quota | paid } YouTube video search with optional "limit" (1-50), "sort": relevance|rating| date|views, "within": hour|today|week|month|year, "duration": short|medium| long. A query with no matching videos is answered FREE — {"known": false}, never charged, never burns quota. Pairs with video-transcript: search here, read there. Over the free quota ≈ $0.02 per call, settled in USDC. POST /api/v1/news/brief {} (no input) ≈ $0.05, pay-only → { as_of, brief, digest, headlines, usage, paid } Today's market-news brief in one call: the platform's newest published news digest synthesized with the live market wire into a cited bullet brief. Free preview of the same raw data: GET /api/v1/pulse. Pre-staged data feeds — built on a schedule, served instantly, no model: GET /api/v1/feeds the catalogue (free): every feed, price, cadence, sections GET /api/v1/feeds/{feed}/sample free: the edition from 24h ago, 10 items per section POST /api/v1/feeds/{feed} { "section"?, "q"?, "ticker"?, "date"?, "at"?, "limit"? } 1–2¢ (or GET with the same as query parameters) → { feed, as_of, edition, next_edition, count, sections: {name: [items]}, sources, paid } news ¢2 hourly 35 publisher feeds grouped into stories by topic (markets, crypto, ai, tech, policy, world, science): outlets, links, first-seen/updated, tickers mentioned, headline tone movers ¢2 15 min US gainers/losers/unusual volume (>$2B) + top crypto movers, each joined to the news stories that mention it filings ¢2 30 min SEC 8-K items in plain words, Form 4 open-market insider trades with $ value, insider cluster buys, 13D, S-1/F-1 calendar ¢1 hourly 8 days of economic releases (forecast/previous/actual) + US earnings (estimates; reported actual vs estimate) prediction-markets ¢1 hourly Polymarket + Kalshi by 24h volume: implied probability, 24h change, volume, close date ai ¢1 hourly new models + API price changes (diffed between editions), LLM price sheet ($/M tokens), trending models, top papers security ¢1 hourly CISA KEV (14d), NVD CVEs (24h, scored), GitHub advisories trending ¢1 hourly Google trending searches, Wikipedia most-read, GitHub trending, Hacker News front page regulation ¢1 hourly Federal Register (rules, proposed rules, presidential docs) + SEC/CFTC/Fed/OCC/FTC/DOJ/ESMA press and OFAC actions crypto-listings ¢2 15 min Binance/OKX listings + delistings, new Coinbase pairs (detected by diff), active Snapshot governance votes Resolved before payment: no edition yet, or filters matching nothing, answer FREE (charged: false). "at" reads a past edition (hourly for 7 days, daily after). POST /api/v1/x402/quote { "symbols": ["NVDA", "BTC-USD", "EURUSD=X"] } ≈ $0.01 → { results: [{symbol, price, change_pct, currency, source, as_of}], missing, paid } POST /api/v1/x402/fetch { "url": "https://…", "extract": "text|links|metadata" } ≈ $0.02 → { status, final_url, text, blocked, hops, paid } Plain HTTP with a browser-grade fingerprint and readable-text extraction; JS-only or bot-walled pages come back flagged (a browser-driven read is a run). Public hosts only. POST /api/v1/x402/chat { "messages": [{"role": "user", "content": "…"}] } ≈ $0.02 → { content, model, finish_reason, usage: {prompt_tokens, completion_tokens}, paid } One completion on the engine the swarm itself runs. OpenAI message shape, bounded: 24 messages in, 24K characters, 1,024 tokens out. No key, no account, no rate-limit dance. ### With an account: the OpenAI-compatible endpoint POST /api/v1/llm/v1/chat/completions (Authorization: Bearer ) GET /api/v1/llm/v1/models Point any OpenAI SDK at `https://ioswarm.io/api/v1/llm/v1` with your API key; `model` may be "swarmio". Metered per call on your credits — no per-call floor, `stream: true` answers in the usual SSE framing. This is the endpoint the apps the swarm builds use for their own AI features. ## 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. Runs take 3–10 minutes; over MCP, runs.wait blocks until done. 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. Hosting — publish a built static site to https://.ioswarm.io and pay a flat credit price for each UTC day it stays online (default $0.10/day at the standard rate card; the exact price per app is in its record). The day's charge lands on the first deploy of the day, then again each UTC midnight the app is online. A charge with an empty balance starts 3 days of grace — the site stays up and the owner is emailed — then the app pauses (grace days are not back-charged) until a top-up and resume. Static only: a zip/tar.gz of a built site (index.html at the root or one folder down), or a finished run whose workspace builds with `npm run build` (Next.js needs output:'export'). GET /hosting/slug-check?slug=my-app — free: { "available": true } or the reason (taken / reserved / invalid). GET /hosting/apps — your hosted apps. GET /hosting/apps/for-run?run_id= — the app this run (or any run it continues) was published to; a continuation remembers where its workspace lives. The app includes current_version_run_id — the run the live build came from. POST /hosting/apps { "slug": "my-app" } — reserve the name. No charge until the first deploy. GET /hosting/apps/:id — one app: status, url, paid_through, deploy state. POST /hosting/apps/:id/deploy — publish a version. Either a raw zip or tar.gz body (Content-Type application/zip or application/gzip, ≤32 MB), or JSON from a run: { "run_id": "" } → 202, the build runs in the background and deploy_state becomes "live" or "failed". Refused with 402 when the balance cannot cover today. A Next.js server app is refused up front unless the body also sets "convert_static": true — then the platform patches output:'export' into the staging copy (workspace untouched) and the build proves it. POST /hosting/apps/:id/pause / resume — pause serves a parked page and stops the daily charge; resume re-charges the day. DELETE /hosting/apps/:id — removes the site and frees the name. ## 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.