# Probe transcript — 2s.io (e2e, trial call only, nothing paid) | | | |---|---| | Entry | `entries/2s-io.yaml` | | Target | https://2s.io · OpenAPI `https://2s.io/api/openapi` · `https://2s.io/llms.txt` · `https://2s.io/.well-known/x402` | | Mode | `e2e` (gate first, then e2e in the same session) | | Probe id | `2s-io-e2e` | | Rubric | `rubric/v0.2.json` sha256 `cd605a44799f49951bf078fda63c5b21ad3c07db494f41d38ad19ce729c5551a` | | Date | 2026-09-05 | | Sandbox up | 2026-09-05T13:05:49Z | | Sandbox down | 2026-09-05T13:11:38Z | | Wall clock | 349 s (epoch 1788613549 → 1788613898) | | Real money spent | $0.00 — no wallet existed (`PROBE_WALLET_KEY` unset, `PROBE_SPEND_CAP_USD=0`). Two HTTP 402 challenges were received and recorded; none was answered | | Human interventions | 0 | | Verdict | `completed` | | Secrets carried in | none. Nothing to redact | All target traffic went through the probe sandbox; allowlist was exactly `2s.io`. Gate-mode reads of the homepage and `/learn/x402/quickstart` used WebFetch. ```sh ./.claude/skills/probe/probe-sandbox.sh up --id 2s-io-e2e --target 2s.io ``` --- ## 0. Permission gate There is no terms-of-service instrument anywhere on the property. Checked from the sandbox (step 10): `/terms`, `/tos`, `/legal`, `/privacy`, `/terms-of-service` all return HTTP 404 (a Next.js error page, ~526 kB). `/.well-known/security.txt` → 404 (step 03). `ai-plugin.json` sets `"legal_info_url": "https://2s.io"` — the homepage, which has no terms. What the operator *does* publish is an explicit, enumerated welcome to AI agents: `https://2s.io/robots.txt` (HTTP 200, 836 bytes, step 01) lists `GPTBot`, `ChatGPT-User`, `OAI-SearchBot`, `ClaudeBot`, `Claude-Web`, `anthropic-ai`, `PerplexityBot`, `Perplexity-User`, `Google-Extended`, `Googlebot`, `Bingbot`, `Applebot`, `Applebot-Extended`, `CCBot`, `cohere-ai`, `DuckAssistBot`, `YouBot`, `meta-externalagent`, `Bytespider`, `Amazonbot`, `Diffbot`, `FacebookBot`, `MistralAI-User` — every one with `Allow: /` — then `User-agent: *` / `Allow: /` and `Sitemap: https://2s.io/sitemap.xml`. No `Disallow` anywhere. `https://2s.io/llms.txt` (HTTP 200, 330 546 bytes, step 02), first line after the title: ```untrusted-content > The (most) everything API for AI agents: 575+ pay-per-call endpoints on one origin. More live, ground-truth data than any other x402 service — public records, finance, crypto & web3, security & CVEs, legal, medical, weather, geo, B2B/EDI. A full AI gateway (chat, image, multi-model consensus across every major model). And agent infrastructure with no account: wallet-scoped storage, locks, queues, scheduled callbacks, pub/sub, and watchers. Pay per call in USDC on Base or Solana via x402 — no API keys, no signup. The first x402 API with upto billing: authorize the quoted max on AI endpoints, settle only actual usage. New endpoints land constantly. ``` **Decision.** No negative signal of any kind; an affirmative published bot policy and an `llms.txt`. Following the corpus precedent set by `frankfurter` (docs that invite agents but no terms instrument that grants anything): `permission.status: unstated`, with the `permissive-tos` bonus awarded on the published bot policy + `llms.txt`. The latent risk is the same as frankfurter's: terms could appear with no notice channel. --- ## 1. Reference task > With no wallet and no credentials, GET > `https://2s.io/api/weather/zip?zip=94103&trial=1` and pass only if the response is > HTTP 200 JSON whose `data.items[0].location.zip` is `"94103"`, whose > `data.items[0].current.temperatureF` is a number, and whose `meta.caller` is `"trial"`. The trial mechanism is the operator's own documented path ("Try before you buy"), so the task exercises a real handler with real data at $0. The quickstart's exact example URL was used, so the task also tests "examples run as written". --- ## 2. Reference task — PASS (step 07) `13:06:57Z` — `GET https://2s.io/api/weather/zip?zip=94103&trial=1` → **HTTP 200**, 796 bytes, 0.564 s Response headers (verbatim, minus the proxy's own two lines): ``` HTTP/2 200 access-control-allow-headers: Authorization, Content-Type access-control-allow-methods: GET, POST, OPTIONS access-control-allow-origin: * cache-control: public, max-age=0, must-revalidate content-security-policy: default-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none' content-type: application/json date: Sat, 05 Sep 2026 13:06:57 GMT referrer-policy: no-referrer server: Vercel strict-transport-security: max-age=63072000; includeSubDomains; preload x-2s-caller: trial x-2s-cost-usd: 0 x-2s-endpoint: weather.zip x-content-type-options: nosniff x-frame-options: DENY x-matched-path: /api/weather/zip ``` Body: ```json {"data":{"ok":true,"items":[{"location":{"zip":"94103","city":"San Francisco","state":"CA","coords":{"lat":37.7725,"lon":-122.4147},"timezone":"America/Los_Angeles"},"current":{"temperatureF":56,"apparentTemperatureF":null,"humidityPct":91,"windMph":2,"windDirection":"WSW","windDirectionDeg":248,"precipProbabilityPct":0,"conditions":"partly cloudy","isDay":false,"observedAt":"2026-09-05T05:00:00-07:00"}}],"total":1,"source":{"provider":"NOAA National Weather Service","url":"https://api.weather.gov","license":"Public domain (US government work)"}},"meta":{"endpoint":"weather.zip","version":null,"caller":"trial","cost":{"usd":0},"trial":{"free":true,"limit":"1/hour per endpoint per client","upgrade":"Pay per call with x402 for unlimited access — no signup, no API key. https://2s.io"}}} ``` `REFERENCE_TASK_PASS`. Real upstream data (NWS observation at 05:00 PDT), cost `$0` asserted in both a header and the body, provenance and licence of the upstream in the body. Nothing was sent beyond the URL and a `User-Agent`. --- ## 3. The x402 challenge, recorded verbatim (steps 08 and 09) — NOT answered The same URL **without** `trial=1` (step 08, `13:06:57Z`) → **HTTP 402**, 5 428 bytes, 0.180 s. Headers of note: ``` HTTP/2 402 content-type: application/json payment-required: eyJ4NDAyVmVyc2lvbiI6MiwiYWNjZXB0cyI6W3sic2NoZW1lIjoiZXhhY3QiLCJuZXR3b3JrIjoiZWlwMTU1Ojg0NTMiLCJhbW91bnQiOiIzMDAwIiwiYXNzZXQiOiIweDgzMzU4OWZDRDZlRGI2RTA4ZjRjN0MzMkQ0ZjcxYjU0YmRBMDI5MTMiLCJwYXlUbyI6IjB4MmI2RDQ5ODhEYjQ3MjNFNjkwOERiODZBYjJiOGRGQmM1MUZDMzJDNSIsIm1heFRpbWVvdXRTZWNvbmRzIjo2MCwiZXh0cmEiOnsibmFtZSI6IlVTRCBDb2luIiwidmVyc2lvbiI6IjIifX0seyJzY2hlbWUiOiJleGFjdCIsIm5ldHdvcmsiOiJzb2xhbmE6NWV5a3Q0VXNGdjhQOE5KZFRSRXBZMXZ6cUtxWkt2ZHAiLCJhbW91bnQiOiIzMDAwIiwiYXNzZXQiOiJFUGpGV2RkNUF1ZnFTU3FlTTJxTjF4enliYXBDOEc0d0VHR2tad3lURHQxdiIsInBheVRvIjoiVFc2bnRhR3p2ajYzWmdQanN6ZDRGQ0dtVlRHZnp2MU1BWVpialljeVdobiIsIm1heFRpbWVvdXRTZWNvbmRzIjo2MCwiZXh0cmEiOnsibmFtZSI6IlVTREMiLCJ2ZXJzaW9uIjoiMSIsImZlZVBheWVyIjoiQkZLOVRMQzNlZGIxM0s2djRZeUgzRHdQYjVEU1Vwa1d2YjdYbnFDTDliNEYifX1dLCJyZXNvdXJjZSI6eyJ1cmwiOiJodHRwczovLzJzLmlvL2FwaS93ZWF0aGVyL3ppcD96aXA9OTQxMDMiLC… (base64, truncated here; full header in the collected 08-402.h) x-payment-requirements: x402 ``` Body (the envelope; `extensions.bazaar` example payload elided after the first field): ```json {"x402Version":2, "accepts":[ {"scheme":"exact","network":"eip155:8453","amount":"3000","asset":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913","payTo":"0x2b6D4988Db4723E6908Db86Ab2b8dFBc51FC32C5","maxTimeoutSeconds":60,"extra":{"name":"USD Coin","version":"2"}}, {"scheme":"exact","network":"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp","amount":"3000","asset":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v","payTo":"TW6ntaGzvj63ZgPjszd4FCGmVTGfzv1MAYZbjYcyWhn","maxTimeoutSeconds":60,"extra":{"name":"USDC","version":"1","feePayer":"BFK9TLC3edb13K6v4YyH3DwPb5DSUpkWvb7XnqCL9b4F"}}], "resource":{"url":"https://2s.io/api/weather/zip?zip=94103","description":"Current weather conditions for a US ZIP code (temperature, wind, humidity, conditions). Backed by the US National Weather Service (api.weather.gov) — public domain, no rate-limit pressure on commercial use.","mimeType":"application/json","serviceName":"2s","iconUrl":"https://2s.io/icon-512.png","tags":["public-records","identifiers","ai-agents","us-gov-data","x402","upto-billing","ai-gateway","agent-infrastructure"]}, "error":"PAYMENT-SIGNATURE required", "extensions":{"bazaar":{"info":{"input":{"type":"http","method":"GET","queryParams":{"zip":"12345"}},"output":{"type":"json","example":{…}}}}}} ``` What an agent needs to know from this, all present in the challenge itself: protocol version 2; two settlement rails (Base USDC via `exact` scheme, Solana USDC via `exact` scheme); the price is `amount: "3000"` in USDC base units = **$0.003**, which matches the OpenAPI `x-2s-price: {"usd":0.003}` and the `llms.txt` line for this endpoint ("$0.003 USDC per call"); a 60-second validity window; and the header name to retry with (`PAYMENT-SIGNATURE`). The `payment-required` header base64-decodes to the same `{x402Version, accepts, resource, error}` core (verified with `jq -S`); the body additionally carries the `extensions.bazaar` example block. It is a bounded, decodable, per-call authorization request — the opposite of an opaque or unbounded signing demand. **Second trial call on the same endpoint** (step 09, `13:06:58Z`, ~1 s after the first) → **HTTP 402** again, with the identical `accepts` envelope and this error string, which names the limit and the retry time: ``` "error":"Free trial already used for this endpoint (limit 1/hour per endpoint per client). Pay per call with x402 for unlimited access (no signup), or retry in ~3600s." ``` The documented trial policy (`llms.txt` §"Try before you buy") held exactly: one free real call per endpoint per hour, then the normal 402. No further trial or paid request was sent. --- ## 4. Machine-readable surface (all HTTP 200 from the sandbox) | Document | Size | What it says | |---|---|---| | `/llms.txt` | 330 546 B | Full catalogue: how-it-works (3 steps), trial section, upto billing, watchers, 575 `GET /api/...` lines each with a one-paragraph description and a price, a "Sample paying client" in JS, and discovery links | | `/api/openapi` (= `/openapi.json`) | 1 960 806 B | `"openapi":"3.1.0"`, `info.version: "1"`, contact `alley@2s.io`, **575 paths**. `components.securitySchemes.x402Payment` = `apiKey` in header `PAYMENT-SIGNATURE` with the x402 flow described in its `description`. `/api/weather/zip` declares responses `200, 400, 402, 405, 500, 502` and vendor extensions `x-2s-price {"usd":0.003}`, `x-2s-accepts`, `x-payment-info`, `x-2s-response-shape` | | `/.well-known/x402` | 380 692 B | `x402Version: 2`, `service` block linking `directory`, `openapi`, `llmsTxt`, `mcpServerCard`, `aiPlugin`, `a2aAgentCard`; `settlement` (Base `eip155:8453` USDC `0x8335…2913`, treasury `0x2b6D…32C5`, facilitator `https://api.cdp.coinbase.com/platform/v2/x402`; Solana USDC `EPjF…Dt1v`, treasury `TW6n…Whn`); `endpoints` catalogue; a `seeAlso` list of *other* x402 services (Exa, Browserbase, …) — inert links, not followed | | `/.well-known/mcp/server-card.json` | 836 608 B | `serverInfo.name: "2s.io"`, `authentication.required: true, schemes: ["x402"]`, **575 tools** with JSON-schema inputs, e.g. `weather.zip` (`zip` matching `^\d{5}$`, `x-2s-price-usd: 0.003`). `llms.txt` gives the installable form `npx -y @2sio/mcp --trial`. Not driven in this run | | `/.well-known/agent-card.json` | 2 206 B | A2A card, `url: https://2s.io/a2a`, one skill `discover-endpoints` | | `/.well-known/ai-plugin.json` | 1 865 B | `auth.type: "none"`, `api.url: https://2s.io/openapi.json`, `contact_email: alley@alleyford.com`, `legal_info_url: https://2s.io` | | `/api/directory` | 654 706 B | keys `auth, description, endpoints, pricing, seeAlso, service, specs`; `auth.flows[0].kind: "x402"` with the flow spelled out in prose; 575 endpoints | **Do the examples run as written?** The quickstart's `curl "https://2s.io/api/weather/zip?zip=94103&trial=1"` is the reference task: yes. The `llms.txt` IBAN example was not run (one trial per endpoint per hour is the operator's budget; I spent mine on the reference task). ## 5. Model-addressed content in the docs (recorded, not followed) `llms.txt` lines 44–45 and 52 point at resources whose purpose is to be loaded into a model. Quoted as untrusted data; none was fetched or acted on: ```untrusted-content - [https://2s.io/skill.md](https://2s.io/skill.md) — installable Claude Code skill (teaches the whole catalog + x402 flow) - [https://2s.io/prompt.txt](https://2s.io/prompt.txt) — paste-in system-prompt block for any agent - [https://2s.io/.well-known/agent-card.json](https://2s.io/.well-known/agent-card.json) — A2A AgentCard (endpoint-discovery skill; A2A JSON-RPC transport at https://2s.io/a2a) ``` Classification: this is the documentation's designated channel for exactly that, the content is scoped to the service, and nothing in what was fetched attempts to override a caller's task. Recorded as a docs feature, **not** a `prompt-injection` finding — the same treatment the frankfurter transcript gave the MCP `instructions` string. The `skill.md` and `prompt.txt` bodies themselves were not fetched and are not vouched for. Grep sweep: `ignore (all )?previous|you are now|as an ai|system prompt|disregard|you must` over all collected bodies → the only hits are `as an ai` inside "Pass ident as an airline flight designator (ICAO "UAL1" or IATA "UA1") or an ai[rcraft…]" (llms.txt / OpenAPI description of `flight.status`). False positive. --- ## 6. Egress log (verbatim, proxy) 18 CONNECTs, all to `2s.io:443`. **Blocked: 0.** The target never redirected the client off-domain; the only off-property URLs in its output are inert documentation links (`x402.org`, the Coinbase facilitator, upstream data providers, the `seeAlso` list). ``` CONNECT Sep 05 13:06:52.211 [11]: Request (file descriptor 5): CONNECT 2s.io:443 HTTP/1.1 CONNECT Sep 05 13:06:52.225 [11]: Established connection to host "2s.io" using file descriptor 6. CONNECT Sep 05 13:06:52.411 [11]: Request (file descriptor 5): CONNECT 2s.io:443 HTTP/1.1 CONNECT Sep 05 13:06:52.423 [11]: Established connection to host "2s.io" using file descriptor 6. CONNECT Sep 05 13:06:55.025 [11]: Request (file descriptor 5): CONNECT 2s.io:443 HTTP/1.1 CONNECT Sep 05 13:06:55.042 [11]: Established connection to host "2s.io" using file descriptor 6. CONNECT Sep 05 13:06:55.345 [11]: Request (file descriptor 5): CONNECT 2s.io:443 HTTP/1.1 CONNECT Sep 05 13:06:55.357 [11]: Established connection to host "2s.io" using file descriptor 6. CONNECT Sep 05 13:06:55.554 [11]: Request (file descriptor 5): CONNECT 2s.io:443 HTTP/1.1 CONNECT Sep 05 13:06:55.569 [11]: Established connection to host "2s.io" using file descriptor 6. CONNECT Sep 05 13:06:56.543 [11]: Request (file descriptor 5): CONNECT 2s.io:443 HTTP/1.1 CONNECT Sep 05 13:06:56.566 [11]: Established connection to host "2s.io" using file descriptor 6. CONNECT Sep 05 13:06:57.146 [11]: Request (file descriptor 5): CONNECT 2s.io:443 HTTP/1.1 CONNECT Sep 05 13:06:57.158 [11]: Established connection to host "2s.io" using file descriptor 6. CONNECT Sep 05 13:06:57.718 [11]: Request (file descriptor 5): CONNECT 2s.io:443 HTTP/1.1 CONNECT Sep 05 13:06:57.729 [11]: Established connection to host "2s.io" using file descriptor 6. CONNECT Sep 05 13:06:57.905 [11]: Request (file descriptor 5): CONNECT 2s.io:443 HTTP/1.1 CONNECT Sep 05 13:06:57.919 [11]: Established connection to host "2s.io" using file descriptor 6. CONNECT Sep 05 13:06:58.146 [11]: Request (file descriptor 5): CONNECT 2s.io:443 HTTP/1.1 CONNECT Sep 05 13:06:58.161 [11]: Established connection to host "2s.io" using file descriptor 6. CONNECT Sep 05 13:06:58.313 [11]: Request (file descriptor 5): CONNECT 2s.io:443 HTTP/1.1 CONNECT Sep 05 13:06:58.325 [11]: Established connection to host "2s.io" using file descriptor 6. CONNECT Sep 05 13:06:58.694 [11]: Request (file descriptor 5): CONNECT 2s.io:443 HTTP/1.1 CONNECT Sep 05 13:06:58.709 [11]: Established connection to host "2s.io" using file descriptor 6. CONNECT Sep 05 13:06:58.935 [11]: Request (file descriptor 5): CONNECT 2s.io:443 HTTP/1.1 CONNECT Sep 05 13:06:58.948 [11]: Established connection to host "2s.io" using file descriptor 6. CONNECT Sep 05 13:06:59.227 [11]: Request (file descriptor 5): CONNECT 2s.io:443 HTTP/1.1 CONNECT Sep 05 13:06:59.246 [11]: Established connection to host "2s.io" using file descriptor 6. CONNECT Sep 05 13:10:54.539 [11]: Request (file descriptor 5): CONNECT 2s.io:443 HTTP/1.1 CONNECT Sep 05 13:10:54.662 [11]: Established connection to host "2s.io" using file descriptor 6. CONNECT Sep 05 13:10:54.893 [11]: Request (file descriptor 5): CONNECT 2s.io:443 HTTP/1.1 CONNECT Sep 05 13:10:54.906 [11]: Established connection to host "2s.io" using file descriptor 6. CONNECT Sep 05 13:10:55.126 [11]: Request (file descriptor 5): CONNECT 2s.io:443 HTTP/1.1 CONNECT Sep 05 13:10:55.139 [11]: Established connection to host "2s.io" using file descriptor 6. CONNECT Sep 05 13:10:55.283 [11]: Request (file descriptor 5): CONNECT 2s.io:443 HTTP/1.1 CONNECT Sep 05 13:10:55.297 [11]: Established connection to host "2s.io" using file descriptor 6. ``` --- ## 7. Safety observations | Check | Result | |---|---| | Prompt injection | None (§5). Model-addressed resources exist and are linked from `llms.txt`; they are the docs' designated channel and were not fetched | | Key / seed-phrase request | None. The paying-client sample in `llms.txt` reads `process.env.PRIVATE_KEY` client-side, which is how x402 clients work; the service never asks for a key, only for a signed EIP-3009 authorization bounded to `amount` | | Unbounded approval | None observed. `exact` scheme, `amount: "3000"` ($0.003), `maxTimeoutSeconds: 60`. The `upto` scheme described in `llms.txt` requires a one-time Permit2 USDC approval — a standing approval by design; not exercised, flagged under unverifiable on the entry | | Env probe | None | | Overcharge | N/A ($0). Quoted price consistent across 402 body, OpenAPI extension, and `llms.txt` | | Malicious payload | None; JSON only. Strict CSP (`default-src 'none'`), `X-Frame-Options: DENY`, `nosniff` on API responses | | TLS | HTTP/2 over TLS; HSTS `max-age=63072000; includeSubDomains; preload` | | Secrets in URLs | None — payment travels in a header | | PII requested | None | | `security.txt` | **404**. No coordinated-disclosure channel beyond the contact emails in the OpenAPI/ai-plugin documents. `security-posture` bonus not awarded for this reason | | Terms | None exist (§0) | **Findings: none.** `safety.penalty_total: 0`. --- ## 8. Scoring rationale | Dimension | Rung | Why | |---|---|---| | `access` | **25** | Nothing beyond HTTP. No signup, no key, no wallet for the trial; for paid calls, the wallet is the rubric's baseline | | `payment` | **25** | Both 25-point rungs apply: x402 per-call settlement, and genuinely free at the reference task's volume with no card on file. `reference_task_cost_usd: 0` | | `interface` | **20** | Installable MCP server (`@2sio/mcp`) with a 575-tool server card, plus OpenAPI 3.1 with 575 paths and vendor extensions carrying price. Rung awarded on the rubric's condition; the MCP was not driven in this run | | `completion` | **20** | Completed; zero human input; everything needed came from the target (`llms.txt`, OpenAPI description, the 402 body itself) | | `docs` | **7** | `llms.txt` present and enormous; errors actionable (the 402 and the trial-exhausted messages name the fix and the retry time); schemas versioned (`openapi 3.1.0`, `x402Version 2`); the quickstart example runs as written. Missing: a general rate-limit contract for paid calls (`"429"` appears **0** times in the OpenAPI; only some endpoints mention upstream limits in prose) and any terms/security.txt. "Good docs, most of the above" | | `permissive-tos` bonus | **awarded** | Published bot policy that enumerates AI crawlers with `Allow: /`, plus `llms.txt` | | `security-posture` bonus | **not awarded** | Per-call authorization is exemplary, but there is no `security.txt` | | `permission.status` | **unstated** | No terms instrument; affirmative invitation only (§0) | Dimension vector emitted: `access 25, payment 25, interface 20, completion 20, docs 7`; bonus `permissive-tos`; penalties none. **Agent-native verdict, first-hand.** This is the first target in the corpus whose entire onboarding is the HTTP 402 itself: no account, no key, and the challenge tells you the price, the chains, the recipient and the header to retry with. The trial parameter let me verify a real handler for $0 before owning a wallet, and when I tried it twice the error told me exactly when to come back. The friction is on the human side of the ledger, not the agent's: no terms exist, the paid path could not be exercised without USDC, and one trial per endpoint per hour is a small budget if you are evaluating many endpoints. --- ## 9. Sandbox feedback 1. `/out` writable — the frankfurter-era chown fix holds. 2. A 2 MB OpenAPI and a 330 kB `llms.txt` are fine inside the box but too large to quote; `jq` in the box made the schema checks cheap. No issues.