# sondahub — full reference (v0.6.1) Free public mock APIs for testing. 78,076 records in 37 collections across 7 APIs; REST, GraphQL, gRPC-Web and Connect, WebSocket, SSE, MQTT over WebSocket, MCP, SCIM 2.0, SAML 2.0, OAuth 2.0 / OIDC. No key, no signup, CORS open to every origin. Docs: https://sondahub.com/ ## Writes and sessions POST, PUT, PATCH and DELETE are validated, run through the real rules and answered as a real server would. No database exists: the answer carries X-Sondahub-Session, a signed, compressed token with every change the client has made (created and changed rows, deleted ids, next ids, idempotency keys). Send the newest token back — header X-Sondahub-Session, or ?_session= — and the request starts from that state: the record comes back on GET, in lists, nested routes and totals, over REST, GraphQL, gRPC, SCIM and MCP alike. Without it every request sees the seed (X-Sondahub-Write: simulated); with it, X-Sondahub-Write: session and "_note": "Kept in your session, not on the server: this write lives in the X-Sondahub-Session token this answer carries. Keep sending the newest token and every request sees it.". Created ids continue after the seed. The token stops growing at 24,000 characters (X-Sondahub-Session-Note says so). Nobody else sees a client's writes. Docs: https://sondahub.com/mock-api-with-persistence/ ## Controls on any request - X-Sondahub-Chaos: latency=800,jitter=200,fail=0.2,status=503,truncate=0.1 (or ?_chaos=, ?_delay=ms, ?_fail=rate, ?_status=code) — delay, failures at a rate (nothing runs; 429/503 carry Retry-After), bodies cut short; X-Sondahub-Chaos-Applied reports - X-Sondahub-RateLimit: 10/60 (or ?_ratelimit=) — clock-based quota: RateLimit-Policy, RateLimit, X-RateLimit-* headers; 429 + Retry-After in the last 20% of each window - Idempotency-Key on writes — inside a session the first answer is replayed (Idempotent-Replayed: true) and another request with the same key → 422 idempotency_key_reused; without a session the same key yields the same new id - ?format=csv|xml|yaml|ndjson|msgpack or the matching Accept — other shapes of any JSON GET answer - Accept: application/problem+json (or ?_errors=problem) — RFC 9457 errors, type = https://api.sondahub.com/v1/problems/{code} - Cursor paging on every list: ?paging=cursor → meta.has_more, next_cursor, prev_cursor; ?cursor=; ?starting_after={id}, ?ending_before={id} - X-Sondahub-Webhook: https://… (or ?_webhook=) on a write — the record the write itself creates, updates or deletes is POSTed there as {id, type: "{api}.{record}.{created|updated|deleted}", created_at, api, data: {object, previous?}}, signed per X-Sondahub-Webhook-Style: standard (webhook-id, webhook-timestamp, webhook-signature v1,base64 HMAC-SHA256 of id.timestamp.body keyed with the base64 after whsec_), stripe (Stripe-Signature t=,v1=hex HMAC of t.body), github (X-Hub-Signature-256 sha256=hex HMAC of body). Secret whsec_c29uZGFodWItcGxheWdyb3VuZC13ZWJob29rLWtleSE= or X-Sondahub-Webhook-Secret. Async with retries after 1/3/8 s (4 s per attempt), or X-Sondahub-Webhook-Mode: sync (outcomes in _webhooks). Public addresses only. POST https://api.sondahub.com/v1/utils/webhooks/send {url, style, secret} sends one sample event; /verify checks a signature. ## Sandboxes: Stripe and Twilio (independent imitations; not affiliated with Stripe or Twilio) Stripe: the paths of api.stripe.com at https://api.sondahub.com (also https://api.sondahub.com/sandbox/stripe/v1). Auth: Bearer (or Basic user) any sk_test_ key; pk_test_ only for POST /v1/payment_methods; live keys refused. Form-encoded params with brackets (JSON also taken), expand[], Idempotency-Key, Stripe-Version echoed, Request-Id. Models customers (CRUD, list by email), payment_methods (type=card from Stripe's test cards or test ids like pm_card_visa; attach, detach), payment_intents (create, confirm, capture incl. partial, cancel, update; statuses requires_payment_method → requires_confirmation → requires_action (3D Secure: next_action.redirect_to_url opens a sandbox page that returns to return_url with redirect_status; the page can't write to the session, so the PI stays requires_action: confirm again to finish, or cancel / confirm with another card on redirect_status=failed) → requires_capture / succeeded; canceled), charges, refunds (whole/partial; charge_already_refunded), products (default_price_data), prices (lookup_key), checkout/sessions (mode=payment; url opens a sondahub test page whose payment sends charge.succeeded, payment_intent.succeeded and checkout.session.completed to registered endpoints and redirects to success_url; that payment is not written to the session, so the session still reads open and those pi_/ch_ ids are not retrievable — fulfil from the event payload), events (last 30 per session), webhook_endpoints (secret whsec_…; deliveries signed Stripe-Signature t=,v1= HMAC-SHA256 of t.body; enabled_events exact types or *, no partial wildcards; retries after 1/3/8 s on a network error, 408, 429 or 5xx), balance (pending always 0). Paths not modelled (subscriptions, invoices, setup_intents, …/search, and any unknown /v1 path sent with a Stripe key) answer a Stripe-shaped 404. Declines answer 402 card_error with code/decline_code and the payment_intent; invalid params 400 invalid_request_error (parameter_unknown, parameter_missing, amount_too_small, payment_intent_unexpected_state); unknown ids 404 resource_missing. Lists: {object: "list", data, has_more, url}, limit/starting_after/ending_before/created (/line_items: no created). SDKs: stripe-node new Stripe(key, {host: "api.sondahub.com"}), stripe-python StripeClient(key, base_addresses={"api": "https://api.sondahub.com"}) or stripe.api_base. OpenAPI 3: https://api.sondahub.com/sandbox/stripe/openapi.json (import it into an API client: every operation, form bodies, seed ids). Docs: https://sondahub.com/sandboxes/stripe/ Twilio: api.twilio.com → https://api.sondahub.com, verify.twilio.com → https://api.sondahub.com/verify, lookups.twilio.com → https://api.sondahub.com/lookups (also under https://api.sondahub.com/sandbox/twilio). Auth: Basic with any Account SID (AC + 32 hex) and any auth token (callbacks are signed with that token). Models Messages (To, From or MessagingServiceSid, Body/MediaUrl, StatusCallback; status follows the clock: queued, sending, sent ≈1.5 s, delivered at 3 s; segments counted GSM-7/UCS-2), Calls (Url or Twiml; queued, ringing 1 s, in-progress 3 s, completed 13 s, duration 10; the Url is fetched at answer; StatusCallbackEvent initiated/ringing/answered/completed), IncomingPhoneNumbers (the account owns +15005550006 and four 555-01xx numbers; buy by PhoneNumber or AreaCode), the Account, Verify v2 (Services, Verifications, VerificationCheck — the approving code is the first CodeLength digits of 1234567890, 123456 by default; 5 sends, 5 checks, 10 minutes), Lookups v2 (valid, country, national_format, Fields=line_type_intelligence). Twilio's test-credential magic numbers answer their documented errors (SMS To +15005550001 21211, …0002 21612, …0003 21408, …0004 21610, …0009 21614; From …0001 21212, …0007 21606, …0008 21611; calls To …0001 21217, …0002 21214, …0003 21215, …0004 21216; From …0001 21212, unowned 21210). Sandbox numbers: +15550100003 (SMS undelivered 30003, call no-answer), +15550100005 (30005, failed), +15550100006 (30006, busy). Callbacks carry X-Twilio-Signature (base64 HMAC-SHA1 of URL + sorted params, key = auth token). POST https://api.sondahub.com/sandbox/twilio/simulate/sms {url, from, to, body, auth_token} sends your webhook an incoming-SMS request and shows the TwiML answer. Sub-resources (…/Messages/{Sid}/Media, …/Calls/{Sid}/Recordings) and other products under /sandbox/twilio/ answer a Twilio-shaped 404 20404. SDK: twilio-python client.api.base_url = "https://api.sondahub.com" (and client.verify / client.lookups). OpenAPI 3: https://api.sondahub.com/sandbox/twilio/openapi.json (all three hosts on one server). Docs: https://sondahub.com/sandboxes/twilio/ In both, writes live in the X-Sondahub-Session token each answer carries (the SDK must send it back — the docs show the hook per SDK); without it every request sees the seed account. ## OpenAPI mock (bring your own spec) GET https://api.sondahub.com/v1/mock?spec={url of an OpenAPI 3.x or Swagger 2.0 file, JSON or YAML, ≤5 MB} describes it and gives a stable base URL https://api.sondahub.com/v1/mock/~{base64url of the spec URL}; requests under it are routed by the spec, validated (parameters, body, presence of declared credentials; 422 / 401 / 405) and answered from the example or generated from the schema, deterministically. Prefer: code=404 | example=name | dynamic=true picks the answer. Writes are not kept. Docs: https://sondahub.com/openapi-mock-server/ ## gRPC-Web and Connect Each API is the protobuf service sondahub.{api}.v1.{Api}Service (sondahub.store.v1.StoreService, sondahub.fleet.v1.FleetService, sondahub.bank.v1.BankService, sondahub.social.v1.SocialService, sondahub.helpdesk.v1.HelpdeskService, sondahub.flights.v1.FlightsService, sondahub.identity.v1.IdentityService), .proto at https://api.sondahub.com/v1/{api}/{api}.proto. Methods per collection: List{Plural}(page, limit, sort, q, filter map) → {data, page, limit, total, pages}; Get{T}(id); Create{T}({T}); Update{T}(id, {singular}); Delete{T}(id). Watch(topics, max_events) is a server stream of live events. Call https://api.sondahub.com/grpc/{service}/{Method} with gRPC-Web (application/grpc-web+proto or grpc-web-text) or Connect (application/json or application/proto; application/connect+json / +proto for streams; GET ?encoding=json&message=… for reads). Native gRPC (application/grpc) answers UNIMPLEMENTED: the platform cannot send HTTP/2 trailers. ## SCIM 2.0 https://api.sondahub.com/scim/v2 over the Identity directory. Authorization: Bearer sonda-scim-token (or an OAuth access token; /Me needs one for a person). Users and Groups: GET (filter with eq ne co sw ew gt ge lt le pr, and/or/not, value paths; sortBy, sortOrder, startIndex, count ≤200, attributes, excludedAttributes), POST, PUT, PATCH (add/replace/remove, Okta and Entra shapes), DELETE, ETag / If-Match; /.search, /Bulk (≤100 ops, bulkId), /Me, /ServiceProviderConfig, /ResourceTypes, /Schemas (no auth). userName is unique (409 uniqueness). Writes keep to the session token. Docs: https://sondahub.com/scim-test-server/ ## SAML 2.0 IdP: metadata and entity ID https://api.sondahub.com/saml/metadata, SSO https://api.sondahub.com/saml/sso (Redirect and POST bindings; IdP-initiated with ?acs=&audience=), SLO https://api.sondahub.com/saml/slo, certificate https://api.sondahub.com/saml/certificate. Signs in any Identity directory user (user_name or email, password probe). Options as query parameters or switches on the sign-in page: sign=both|assertion|response|none, nameid=request|email|persistent|transient|unspecified, attrs=basic|claims|oid, encrypt=none|gcm|cbc (for the test SP, or sp_metadata=URL), outcome=success|AuthnFailed|RequestDenied|expired|tampered; auto=1 skips the page, format=json answers JSON. Test SP: https://api.sondahub.com/saml/sp (start with ?idp=SSO URL), ACS https://api.sondahub.com/saml/acs, entity ID and metadata https://api.sondahub.com/saml/sp/metadata — every response is decoded, decrypted, signature-checked (RSA-SHA1/256/512, exclusive or inclusive c14n) and judged condition by condition (format=json for JSON). https://api.sondahub.com/saml/decode decodes any SAML message. Docs: https://sondahub.com/saml-test-idp/ ## REST conventions (every collection) - GET https://api.sondahub.com/v1/{api}/{collection} — a page: {"data": [...], "meta": {"page","limit","total","pages"}}, X-Total-Count and Link headers - GET https://api.sondahub.com/v1/{api}/{collection}/{id} — one record, with an ETag (If-None-Match → 304) - POST https://api.sondahub.com/v1/{api}/{collection} — create: 201 + Location; 422 lists each wrong field - PATCH / PUT / DELETE https://api.sondahub.com/v1/{api}/{collection}/{id} - GET https://api.sondahub.com/v1/{api}/{collection}/{id}/{relation} — related records - page, limit (1–200, default 20), offset; sort=a,-b; field=value; suffixes _ne _gt _gte _lt _lte _like _in _null; a.b=value inside JSON fields; q= full-text; fields=a,b; expand=relation,relation - Errors: {"error": {"code", "message", "details"}}; 400 for an unknown field, 404 missing id, 405 with Allow, 409 conflicts, 422 validation - OpenAPI 3.0.3 per API: https://api.sondahub.com/v1/{api}/openapi.json ## GraphQL (per API) POST https://api.sondahub.com/v1/{api}/graphql {"query","variables"} or GET ?query=. Introspection on; SDL at ?sdl. Each collection: a paged list query (limit, page, sort, q, filter with operator suffixes like price_lt) returning {total, data}, a by-id query, relation fields both ways, and createX / updateX / replaceX / deleteX mutations (simulated; the note is in extensions). No subscriptions — use the streams. ## Live streams (per API) - WebSocket wss://api.sondahub.com/v1/{api}/ws?topics=a,b — hello, then {"type":"event","topic","api","ts","data"} about once a second; send {"type":"subscribe","topics":[...]}, {"type":"ping"} → pong, anything else is echoed as {"type":"echo"} - SSE https://api.sondahub.com/v1/{api}/events?topics=a,b — the same events, named by topic, with ids; Last-Event-ID resumes the numbering - Echo socket wss://api.sondahub.com/v1/utils/ws (text and binary); SSE clock https://api.sondahub.com/v1/utils/sse?count=10&interval=1000 ## Store API — fake e-commerce API An online shop: products, customers, orders, reviews and stock. Two thousand products in eight categories, eight hundred customers, three thousand orders with their line items, reviews, three warehouses of stock and open carts. Orders move through a lifecycle (pending → paid → shipped → delivered) and the live stream shows orders moving. Base https://api.sondahub.com/v1/store · OpenAPI https://api.sondahub.com/v1/store/openapi.json · GraphQL https://api.sondahub.com/v1/store/graphql · WS wss://api.sondahub.com/v1/store/ws · SSE https://api.sondahub.com/v1/store/events · Docs https://sondahub.com/apis/store/ Live topics: orders (An order changing status (paid → shipped → delivered). every 4 s); inventory (A stock level moving at a warehouse. every 6 s) ### store/categories — 8 records The eight product categories. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; name string required; slug string required; description text; product_count int read-only Relations (expand / nested route): products → many products via category_id Data file: https://api.sondahub.com/data/store/categories.json · Page: https://sondahub.com/apis/store/categories/ ### store/products — 2,000 records What the store sells. Prices are in USD. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; sku string required; name string required; slug string; description text; category_id ref → categories required; brand string; price float required; compare_at_price float; currency string; color string; weight_g int; tags json string[]; in_stock bool; rating float read-only; review_count int read-only; status enum(active|draft|archived) Relations (expand / nested route): category → one categories via category_id; reviews → many reviews via product_id; inventory → many inventory via product_id Data file: https://api.sondahub.com/data/store/products.json · Page: https://sondahub.com/apis/store/products/ ### store/customers — 800 records People who buy. Addresses are nested objects; filter on them with a dotted name (?address.country=AR). Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; name string required; email string required; phone string; company string; address json { line1, line2?, city, region, postal_code, country }; tier enum(standard|silver|gold|platinum); marketing_opt_in bool; orders_count int read-only; total_spent float read-only; notes text Relations (expand / nested route): orders → many orders via customer_id; reviews → many reviews via customer_id Data file: https://api.sondahub.com/data/store/customers.json · Page: https://sondahub.com/apis/store/customers/ ### store/orders — 3,000 records An order and its money. Line items live in order_items (also at /orders/{id}/items and ?expand=items). Rules: POST may carry items: [{ product_id, quantity }]: the hub prices them from the products, computes subtotal, shipping, tax and total, creates the order items and returns them inline. PATCH to shipped stamps shipped_at and a tracking number; delivered stamps delivered_at. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; number string read-only; customer_id ref → customers required; status enum(pending|paid|shipped|delivered|cancelled|refunded); subtotal float; shipping float; tax float; discount float; total float; currency string; payment_method enum(card|paypal|bank_transfer|cash_on_delivery); shipping_address json { line1, line2?, city, region, postal_code, country }; shipping_method enum(standard|express|overnight|pickup); tracking_number string; placed_at datetime; paid_at datetime; shipped_at datetime; delivered_at datetime; notes text Relations (expand / nested route): customer → one customers via customer_id; items → many order_items via order_id Data file: https://api.sondahub.com/data/store/orders.json · Page: https://sondahub.com/apis/store/orders/ ### store/order_items — 6,219 records One product line on an order. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; order_id ref → orders required; product_id ref → products required; sku string; name string; quantity int required; unit_price float; line_total float Relations (expand / nested route): order → one orders via order_id; product → one products via product_id Data file: https://api.sondahub.com/data/store/order_items.json · Page: https://sondahub.com/apis/store/order_items/ ### store/reviews — 2,500 records Customer reviews, 1–5 stars. Rules: POST recomputes the product’s rating and review_count — in the answer’s world, which ends with the answer. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; product_id ref → products required; customer_id ref → customers required; rating int required; title string; body text; verified_purchase bool; helpful_votes int Relations (expand / nested route): product → one products via product_id; customer → one customers via customer_id Data file: https://api.sondahub.com/data/store/reviews.json · Page: https://sondahub.com/apis/store/reviews/ ### store/warehouses — 3 records Where stock sits. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; code string required; name string required; address json { line1, city, region, postal_code, country }; timezone string Relations (expand / nested route): inventory → many inventory via warehouse_id Data file: https://api.sondahub.com/data/store/warehouses.json · Page: https://sondahub.com/apis/store/warehouses/ ### store/inventory — 6,000 records Stock of a product at a warehouse. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; product_id ref → products required; warehouse_id ref → warehouses required; on_hand int required; reserved int; reorder_point int; bin string; counted_at datetime Relations (expand / nested route): product → one products via product_id; warehouse → one warehouses via warehouse_id Data file: https://api.sondahub.com/data/store/inventory.json · Page: https://sondahub.com/apis/store/inventory/ ### store/carts — 200 records Open shopping carts; items are a nested array, so a cart is one document. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; customer_id ref → customers; session_id string; status enum(open|abandoned|converted); items json { product_id, sku, name, quantity, unit_price }[]; item_count int; subtotal float; coupon string; last_activity_at datetime Relations (expand / nested route): customer → one customers via customer_id Data file: https://api.sondahub.com/data/store/carts.json · Page: https://sondahub.com/apis/store/carts/ ## Fleet API — fake IoT API An IoT fleet: sites, devices, telemetry and alerts — the MQTT one. Five hundred devices of eight kinds across forty sites. The readings table holds the last window of telemetry; the live stream and the MQTT broker publish fresh readings every few seconds and accept commands back. Devices report firmware, signal and battery so there is always something to alert on. Base https://api.sondahub.com/v1/fleet · OpenAPI https://api.sondahub.com/v1/fleet/openapi.json · GraphQL https://api.sondahub.com/v1/fleet/graphql · WS wss://api.sondahub.com/v1/fleet/ws · SSE https://api.sondahub.com/v1/fleet/events · Docs https://sondahub.com/apis/fleet/ Live topics: telemetry (A fresh reading from one of the devices, with its serial, type and metrics. every 2 s); alerts (An alert opening or resolving. every ~15 s); devices (A device going online, offline or degraded. every ~10 s) ### fleet/sites — 40 records A place devices are installed. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; code string required; name string required; kind enum(warehouse|office|plant|store|datacenter|farm|clinic|depot); address json { line1, city, region, postal_code, country }; timezone string; lat float; lon float; device_count int read-only; status enum(active|maintenance|decommissioned) Relations (expand / nested route): devices → many devices via site_id Data file: https://api.sondahub.com/data/fleet/sites.json · Page: https://sondahub.com/apis/fleet/sites/ ### fleet/devices — 500 records A device on a site. Its MQTT topics are fleet/{serial}/telemetry (published by the broker) and fleet/{serial}/commands (you publish). Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; serial string required; name string; type enum(thermostat|power_meter|air_quality|water_meter|gateway|door_sensor|vibration|gps_tracker) required; model string; site_id ref → sites required; firmware string; status enum(online|offline|degraded|provisioning|retired); battery_pct int; rssi_dbm int; ip string; mac string; tags json string[]; config json { report_interval_s, thresholds? }; installed_at datetime; last_seen_at datetime Relations (expand / nested route): site → one sites via site_id; readings → many readings via device_id; alerts → many alerts via device_id Data file: https://api.sondahub.com/data/fleet/devices.json · Page: https://sondahub.com/apis/fleet/devices/ ### fleet/readings — 4,908 records Telemetry. metrics is an object whose keys depend on the device type (a thermostat reports temperature, humidity and setpoint; a power meter voltage, current, power_kw and energy_kwh). Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; device_id ref → devices required; recorded_at datetime required; metrics json { [metric]: number | boolean } required; quality enum(good|estimated|suspect) Relations (expand / nested route): device → one devices via device_id Data file: https://api.sondahub.com/data/fleet/readings.json · Page: https://sondahub.com/apis/fleet/readings/ ### fleet/alerts — 500 records Something a device or a site needs looked at. Acknowledge one with PATCH {"status":"acknowledged"}. Rules: PATCH to acknowledged or resolved stamps the matching timestamp. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; device_id ref → devices required; site_id ref → sites; severity enum(info|warning|critical) required; kind enum(offline|low_battery|threshold|tamper|firmware|weak_signal) required; message string; status enum(open|acknowledged|resolved|muted); opened_at datetime; acknowledged_at datetime; resolved_at datetime; acknowledged_by string Relations (expand / nested route): device → one devices via device_id; site → one sites via site_id Data file: https://api.sondahub.com/data/fleet/alerts.json · Page: https://sondahub.com/apis/fleet/alerts/ ### fleet/commands — 300 records A command sent to a device (reboot, set a config value, request a report). POST one and the answer carries the device’s acknowledgement. Rules: POST answers acked with a result when the device is online (a real device would take a second; with nothing stored there is no later, so the answer is the acknowledgement), queued otherwise. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; device_id ref → devices required; action enum(reboot|set_config|report_now|update_firmware|identify) required; params json object; status enum(queued|sent|acked|failed|expired); issued_by string; sent_at datetime; acked_at datetime; result json object Relations (expand / nested route): device → one devices via device_id Data file: https://api.sondahub.com/data/fleet/commands.json · Page: https://sondahub.com/apis/fleet/commands/ ### MQTT (Fleet) Broker wss://api.sondahub.com/mqtt — WebSocket only (subprotocol mqtt), MQTT 3.1.1 and 5, any username/password, QoS 0 and 1 (2 delivered at 1), retained messages, + and # wildcards, topic aliases. Each connection gets its own world: what you publish reaches your own subscriptions, not other clients'. - broker publishes fleet/{serial}/telemetry: Readings, JSON, every 2 s from a rotating set of devices. Subscribe to fleet/+/telemetry for all of them. - broker publishes fleet/{serial}/status: Retained. online | offline | degraded, changing now and then. - broker publishes fleet/{serial}/ack: The answer to a command you published. - broker publishes fleet/alerts: Alerts as they open and resolve. - you publish fleet/{serial}/commands: Publish {"action":"reboot"} (or set_config, report_now, identify) and the device acknowledges on fleet/{serial}/ack. - you publish anything else: A plain broker: whatever you publish reaches your own matching subscriptions, retained flag honoured. ## Bank API — fake banking API Retail banking: customers, accounts, cards, nearly eight thousand transactions, transfers and FX rates. Accounts carry real running balances. POST a transfer between two accounts and the hub debits one, credits the other and writes both transactions; carry the session token and the balances stay moved. FX rates tick over the live stream. Base https://api.sondahub.com/v1/bank · OpenAPI https://api.sondahub.com/v1/bank/openapi.json · GraphQL https://api.sondahub.com/v1/bank/graphql · WS wss://api.sondahub.com/v1/bank/ws · SSE https://api.sondahub.com/v1/bank/events · Docs https://sondahub.com/apis/bank/ Live topics: fx (A rate ticking: pair, rate, bid, ask. every 1 s); transactions (A card payment posting on one of the seed accounts. every 3 s) ### bank/customers — 400 records Account holders. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; name string required; email string required; phone string; date_of_birth date; address json { line1, line2?, city, region, postal_code, country }; segment enum(retail|premium|business|student); kyc_status enum(pending|verified|rejected); risk_score int Relations (expand / nested route): accounts → many accounts via customer_id; cards → many cards via customer_id Data file: https://api.sondahub.com/data/bank/customers.json · Page: https://sondahub.com/apis/bank/customers/ ### bank/accounts — 640 records Checking, savings, credit and loan accounts. balance is the current balance after every transaction. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; number string read-only; iban string read-only; customer_id ref → customers required; type enum(checking|savings|credit|loan) required; nickname string; currency string; balance float; available float; credit_limit float; interest_rate float; status enum(active|frozen|closed); opened_at date Relations (expand / nested route): customer → one customers via customer_id; transactions → many transactions via account_id; cards → many cards via account_id Data file: https://api.sondahub.com/data/bank/accounts.json · Page: https://sondahub.com/apis/bank/accounts/ ### bank/cards — 505 records Debit and credit cards on an account. Numbers are masked; the last four are real digits of the seed. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; account_id ref → accounts required; customer_id ref → customers required; brand enum(visa|mastercard|amex); type enum(debit|credit|virtual); masked_number string read-only; last4 string read-only; holder_name string; expires string; status enum(active|blocked|expired|lost); contactless bool; daily_limit float Relations (expand / nested route): account → one accounts via account_id; customer → one customers via customer_id Data file: https://api.sondahub.com/data/bank/cards.json · Page: https://sondahub.com/apis/bank/cards/ ### bank/transactions — 7,674 records Every movement on an account. amount is signed: negative leaves the account. balance_after is the running balance. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; account_id ref → accounts required; reference string read-only; type enum(card|transfer|deposit|withdrawal|fee|interest|payment|refund); amount float required; currency string; balance_after float read-only; description string; merchant string; category enum(groceries|dining|transport|fuel|shopping|utilities|entertainment|health|travel|subscriptions|transfer|income|fees|other); status enum(pending|posted|reversed); card_id ref → cards; counterparty json { name, account_number? }; booked_at datetime required; value_date date Relations (expand / nested route): account → one accounts via account_id; card → one cards via card_id Data file: https://api.sondahub.com/data/bank/transactions.json · Page: https://sondahub.com/apis/bank/transactions/ ### bank/transfers — 800 records Money moving between two accounts. POST {"from_account_id","to_account_id","amount","description"} and the hub checks the funds, debits, credits, and writes both transactions. Insufficient funds answers 422. Rules: POST checks both accounts exist, are active, hold the same currency and that the amount is available, then debits, credits and writes a transaction on each side; the answer carries debit_transaction_id and credit_transaction_id. Anything wrong answers 422 with the reason. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; from_account_id ref → accounts required; to_account_id ref → accounts required; amount float required; currency string; description string; status enum(completed|pending|failed|reversed) read-only; debit_transaction_id ref → transactions read-only; credit_transaction_id ref → transactions read-only; scheduled_for date; executed_at datetime read-only Relations (expand / nested route): from_account → one accounts via from_account_id; to_account → one accounts via to_account_id Data file: https://api.sondahub.com/data/bank/transfers.json · Page: https://sondahub.com/apis/bank/transfers/ ### bank/fx_rates — 28 records Exchange rates against USD and the main crosses. The live stream ticks them. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; pair string required; base string required; quote string required; rate float required; bid float; ask float; change_24h_pct float; as_of datetime Data file: https://api.sondahub.com/data/bank/fx_rates.json · Page: https://sondahub.com/apis/bank/fx_rates/ ## Social API — fake social media API A social network: users, posts, comments, likes and follows — the GraphQL one. Eight hundred users, four thousand posts, eight thousand comments and the likes and follows between them. Deeply related, which is what GraphQL is for: a user, their posts, each post’s comments and their authors in one query. Base https://api.sondahub.com/v1/social · OpenAPI https://api.sondahub.com/v1/social/openapi.json · GraphQL https://api.sondahub.com/v1/social/graphql · WS wss://api.sondahub.com/v1/social/ws · SSE https://api.sondahub.com/v1/social/events · Docs https://sondahub.com/apis/social/ Live topics: posts (A new post from one of the seed users. every 5 s); likes (Someone liking something. every 2 s) ### social/users — 800 records Members. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; username string required; display_name string required; email string; bio text; avatar_url string; location string; website string; verified bool; private bool; followers_count int read-only; following_count int read-only; posts_count int read-only; joined_at datetime Relations (expand / nested route): posts → many posts via author_id; comments → many comments via author_id; likes → many likes via user_id; followers → many follows via followee_id; following → many follows via follower_id Data file: https://api.sondahub.com/data/social/users.json · Page: https://sondahub.com/apis/social/users/ ### social/posts — 4,000 records What people write. hashtags is an array; filter with ?hashtags_like=coffee. Rules: POST fills defaults (public, empty hashtags, now) and bumps the author’s posts_count. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; author_id ref → users required; body text required; hashtags json string[]; media json { kind, url, alt? }[]; visibility enum(public|followers|private); reply_to_id ref → posts; likes_count int read-only; comments_count int read-only; reposts_count int read-only; language string; published_at datetime; edited_at datetime Relations (expand / nested route): author → one users via author_id; comments → many comments via post_id; likes → many likes via post_id; reply_to → one posts via reply_to_id Data file: https://api.sondahub.com/data/social/posts.json · Page: https://sondahub.com/apis/social/posts/ ### social/comments — 8,000 records Comments on posts; a comment can answer another comment through parent_id. Rules: POST bumps the post’s comments_count; DELETE lowers it. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; post_id ref → posts required; author_id ref → users required; parent_id ref → comments; body text required; likes_count int read-only; flagged bool Relations (expand / nested route): post → one posts via post_id; author → one users via author_id; parent → one comments via parent_id; replies → many comments via parent_id Data file: https://api.sondahub.com/data/social/comments.json · Page: https://sondahub.com/apis/social/comments/ ### social/likes — 8,000 records A user liking a post. POST one to like; DELETE it to unlike. Rules: POST refuses a second like of the same post by the same user (409 already_liked) and bumps the post’s likes_count; DELETE lowers it. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; user_id ref → users required; post_id ref → posts required Relations (expand / nested route): user → one users via user_id; post → one posts via post_id Data file: https://api.sondahub.com/data/social/likes.json · Page: https://sondahub.com/apis/social/likes/ ### social/follows — 5,000 records follower_id follows followee_id. Rules: POST refuses following yourself and duplicates (409); following a private user starts pending; active follows move both users’ counters. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; follower_id ref → users required; followee_id ref → users required; status enum(active|pending|blocked); notifications bool Relations (expand / nested route): follower → one users via follower_id; followee → one users via followee_id Data file: https://api.sondahub.com/data/social/follows.json · Page: https://sondahub.com/apis/social/follows/ ## Helpdesk API — fake helpdesk API A support desk: tickets, messages, agents, customers, SLAs — the state-machine one. Two thousand tickets with their message threads, twenty-five agents in four teams and three hundred customer companies. A ticket moves open → pending → resolved → closed; PATCH its status and the hub checks the transition and stamps the timestamps. Base https://api.sondahub.com/v1/helpdesk · OpenAPI https://api.sondahub.com/v1/helpdesk/openapi.json · GraphQL https://api.sondahub.com/v1/helpdesk/graphql · WS wss://api.sondahub.com/v1/helpdesk/ws · SSE https://api.sondahub.com/v1/helpdesk/events · Docs https://sondahub.com/apis/helpdesk/ Live topics: tickets (A ticket opening, being assigned, or changing status. every 5 s); messages (A new message on a ticket. every 4 s) ### helpdesk/teams — 4 records Support teams. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; name string required; slug string; timezone string; hours string Relations (expand / nested route): agents → many agents via team_id Data file: https://api.sondahub.com/data/helpdesk/teams.json · Page: https://sondahub.com/apis/helpdesk/teams/ ### helpdesk/agents — 25 records The people answering tickets. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; name string required; email string required; team_id ref → teams required; role enum(agent|senior|lead|admin); status enum(available|busy|away|offline); skills json string[]; open_tickets int read-only; rating float read-only Relations (expand / nested route): team → one teams via team_id; tickets → many tickets via assignee_id Data file: https://api.sondahub.com/data/helpdesk/agents.json · Page: https://sondahub.com/apis/helpdesk/agents/ ### helpdesk/customers — 300 records Companies with a support contract. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; name string required; domain string; plan enum(free|starter|business|enterprise); contact_name string; contact_email string; sla_hours int; open_tickets int read-only; satisfaction float read-only Relations (expand / nested route): tickets → many tickets via customer_id Data file: https://api.sondahub.com/data/helpdesk/customers.json · Page: https://sondahub.com/apis/helpdesk/customers/ ### helpdesk/tickets — 2,000 records A support request. Allowed status moves: open → pending | resolved; pending → open | resolved; resolved → closed | open; closed → open (reopen). Anything else answers 422. Rules: PATCH checks the status move (open → pending | resolved; pending → open | resolved; resolved → closed | open; closed → open) and answers 422 invalid_transition otherwise; resolving and closing stamp their timestamps, assigning stamps first_response_at. Counters on the customer and agent follow. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; number string read-only; subject string required; description text; status enum(open|pending|resolved|closed); priority enum(low|normal|high|urgent); category enum(billing|account|bug|feature|howto); channel enum(email|chat|phone|web|api); customer_id ref → customers required; requester_email string; assignee_id ref → agents; team_id ref → teams; tags json string[]; first_response_at datetime read-only; resolved_at datetime read-only; closed_at datetime read-only; due_at datetime; sla_breached bool read-only; satisfaction int; message_count int read-only Relations (expand / nested route): customer → one customers via customer_id; assignee → one agents via assignee_id; team → one teams via team_id; messages → many messages via ticket_id Data file: https://api.sondahub.com/data/helpdesk/tickets.json · Page: https://sondahub.com/apis/helpdesk/tickets/ ### helpdesk/messages — 5,069 records The thread on a ticket, in order. author_type says who wrote it. Rules: POST bumps the ticket’s message_count; an agent’s first message stamps first_response_at; a customer message reopens a pending ticket. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; ticket_id ref → tickets required; author_type enum(customer|agent|system) required; author_id ref → agents; author_name string; body text required; internal bool; attachments json { name, size, content_type }[] Relations (expand / nested route): ticket → one tickets via ticket_id; author → one agents via author_id Data file: https://api.sondahub.com/data/helpdesk/messages.json · Page: https://sondahub.com/apis/helpdesk/messages/ ## Flights API — fake flight booking API Airports, airlines, two and a half thousand scheduled flights and their bookings — the live-board one. Fifty real airports, six invented airlines, flights over a two-week window around the seed’s "today" (2026-09-01), and three and a half thousand bookings with passengers and seats. The live stream runs a departures board: flights boarding, departing, delayed and landing. POST a booking and the hub picks a seat. Base https://api.sondahub.com/v1/flights · OpenAPI https://api.sondahub.com/v1/flights/openapi.json · GraphQL https://api.sondahub.com/v1/flights/graphql · WS wss://api.sondahub.com/v1/flights/ws · SSE https://api.sondahub.com/v1/flights/events · Docs https://sondahub.com/apis/flights/ Live topics: board (The departures board: a flight boarding, departing, delayed, landing or cancelled. every 3 s); bookings (A seat being booked or checked in on a seed flight. every 6 s) ### flights/airports — 50 records Airports by IATA code. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; iata string required; name string required; city string; country string; timezone string; lat float; lon float; terminals int Data file: https://api.sondahub.com/data/flights/airports.json · Page: https://sondahub.com/apis/flights/airports/ ### flights/airlines — 6 records Carriers. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; code string required; name string required; alliance string; fleet_size int Relations (expand / nested route): flights → many flights via airline_id Data file: https://api.sondahub.com/data/flights/airlines.json · Page: https://sondahub.com/apis/flights/airlines/ ### flights/flights — 2,500 records A scheduled flight. Times are ISO with the airport’s UTC offset applied, so a departure reads like the board. Filter by route with ?origin=MIA&destination=EZE, by day with ?departure_date=2026-09-02. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; number string required; airline_id ref → airlines required; origin string required; destination string required; origin_airport_id ref → airports; destination_airport_id ref → airports; departure_date date; scheduled_departure datetime required; scheduled_arrival datetime; estimated_departure datetime; actual_departure datetime; status enum(scheduled|boarding|departed|in_air|landed|delayed|cancelled|diverted); delay_minutes int; gate string; terminal string; aircraft string; duration_minutes int; distance_km int; seats_total int; seats_available int read-only; base_fare float Relations (expand / nested route): airline → one airlines via airline_id; origin_airport → one airports via origin_airport_id; destination_airport → one airports via destination_airport_id; bookings → many bookings via flight_id Data file: https://api.sondahub.com/data/flights/flights.json · Page: https://sondahub.com/apis/flights/flights/ ### flights/bookings — 3,500 records A seat on a flight. POST {"flight_id","passenger":{...},"cabin"} and the hub assigns a seat and a record locator. Cancel with PATCH {"status":"cancelled"}. Rules: POST needs passenger.first_name and last_name, refuses cancelled or departed flights and sold-out ones (409), picks a free seat in the cabin when you give none, prices the fare from the flight, and mints a six-character locator. Cancelling gives the seat back. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; locator string read-only; flight_id ref → flights required; passenger json { first_name, last_name, email, document? } required; seat string; cabin enum(economy|premium|business|first); status enum(confirmed|checked_in|boarded|cancelled|no_show); fare float; currency string; bags int; frequent_flyer string; special_requests json string[]; booked_at datetime; checked_in_at datetime Relations (expand / nested route): flight → one flights via flight_id Data file: https://api.sondahub.com/data/flights/bookings.json · Page: https://sondahub.com/apis/flights/bookings/ ## Identity API — fake user directory API A company directory: people, groups and memberships — behind SCIM 2.0, SAML and OpenID Connect. Three hundred people at a made-up company, Orbit Labs, in forty groups — departments, teams, roles, offices — with managers, titles and employee numbers. The same directory answers SCIM 2.0 at /scim/v2, signs people in over SAML at /saml/sso and fills OpenID Connect userinfo, so the person SCIM lists is the person who logs in. Base https://api.sondahub.com/v1/identity · OpenAPI https://api.sondahub.com/v1/identity/openapi.json · GraphQL https://api.sondahub.com/v1/identity/graphql · WS wss://api.sondahub.com/v1/identity/ws · SSE https://api.sondahub.com/v1/identity/events · Docs https://sondahub.com/apis/identity/ Live topics: directory (Someone joining, leaving, changing team, or being deactivated or reactivated. every ~6 s); logins (A sign-in: who, how (SAML, OIDC, password), from where, and whether it worked. every 2 s) ### identity/users — 300 records People. user_name is unique and is the login everywhere (SAML, OIDC password grant: password "probe"); email is user_name@orbit.example. Rules: POST and PATCH keep user_name and email unique (409 already_exists, naming the user who has it); display_name defaults to the two names and active to true. The same people sign in through SAML and OIDC and are provisioned over SCIM. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; user_name string required; given_name string required; family_name string required; display_name string; email string required; title string; department string; employee_number string; manager_id ref → users; phone string; locale string; timezone string; office string; active bool; external_id string; last_login_at datetime Relations (expand / nested route): manager → one users via manager_id; reports → many users via manager_id; memberships → many memberships via user_id Data file: https://api.sondahub.com/data/identity/users.json · Page: https://sondahub.com/apis/identity/users/ ### identity/groups — 40 records Departments, teams, roles and offices. member_count follows the memberships. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; display_name string required; slug string; kind enum(department|team|role|office|list); description text; external_id string; member_count int read-only Relations (expand / nested route): memberships → many memberships via group_id Data file: https://api.sondahub.com/data/identity/groups.json · Page: https://sondahub.com/apis/identity/groups/ ### identity/memberships — 1,457 records A person in a group. POST one to add someone (409 if they are in it already); DELETE it to take them out. Rules: POST refuses a second membership of the same user in the same group (409 already_member), defaults role to member and bumps the group’s member_count; DELETE lowers it. Fields: id int read-only; created_at datetime read-only; updated_at datetime read-only; user_id ref → users required; group_id ref → groups required; role enum(member|owner) Relations (expand / nested route): user → one users via user_id; group → one groups via group_id Data file: https://api.sondahub.com/data/identity/memberships.json · Page: https://sondahub.com/apis/identity/memberships/ ## MCP server https://api.sondahub.com/mcp — Streamable HTTP, stateless (no MCP session; GET answers 405), protocol versions 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05, no auth. JSON answers; text/event-stream only for the streaming tool. Write tools answer as a real server would and put the change in the HTTP answer's X-Sondahub-Session token (send it back to see it; calls in one JSON-RPC batch share it). Wrong arguments → a tool result with isError: true naming each problem. OAuth-protected twin: https://api.sondahub.com/mcp/secure — 401 with WWW-Authenticate resource_metadata=https://api.sondahub.com/.well-known/oauth-protected-resource/mcp/secure (RFC 9728); authorization server https://api.sondahub.com (RFC 8414 metadata at /.well-known/oauth-authorization-server, OIDC discovery too) with dynamic client registration at https://api.sondahub.com/v1/utils/oauth/register (RFC 7591; the client_id carries its signed registration), PKCE, resource indicators (RFC 8707: the token's aud must be https://api.sondahub.com/mcp/secure). Scopes mcp:read (reads) and mcp:write (write tools; else 403 insufficient_scope). Sign in as sonda/probe or any directory user_name/probe. Docs: https://sondahub.com/mcp-oauth-test-server/ Tools (26): - describe_api(api) — The collections of one sondahub API with their record counts, fields (types, required, read-only, allowed values) and relations. Start here to learn what list_records and the other data tools can ask for. - list_records(api, collection, filters?, q?, sort?, page?, limit?, fields?, expand?) — A page of records from any collection, with filters, search, sorting, field selection and embedded relations. Filters are an object whose keys are field names, optionally with a suffix: _ne, _gt, _gte, _lt, _lte, _like (contains, case-insensitive), _in (a list), _null (true/false); a dotted key reaches into a JSON field (address.country). Example: {"price_lt": 20, "in_stock": true, "category_id_in": [1, 6]}. - get_record(api, collection, id, expand?) — One record by id, optionally with relations embedded. - create_record(api, collection, record) — Create a record in any collection. Validated and answered exactly as the REST API answers a POST — ids, computed fields, related records — but simulated: nothing is stored. describe_api lists the fields. - update_record(api, collection, id, changes, replace?) — Change fields of a record (or replace it whole with replace: true). Validated and answered as the REST API answers a PATCH or PUT, including status rules such as a ticket’s allowed moves — simulated, nothing is stored. - delete_record(api, collection, id) — Delete a record — simulated: the answer is what would have been removed; the record is still there afterwards. - store_search_products(query?, category?, min_price?, max_price?, in_stock?, sort?, limit?) — Search the store’s active products by words, category, price and stock. - store_order_status(order_number) — Where an order is: status, dates, tracking, the customer and the lines, by order number (SH-100042). - bank_account_summary(account_id, transactions?) — An account with its holder, cards and latest transactions. - bank_transfer(from_account_id, to_account_id, amount, description?) — Move money between two accounts of the same currency. Checks both accounts and the funds, then answers with the transfer and both transactions. The balances move for whoever carries the session token. - bank_convert_currency(amount, from, to) — Convert an amount with the bank’s FX table, directly or across USD. - fleet_device_status(serial, readings?) — A device by serial: its site, latest readings and open alerts. - fleet_send_command(serial, action, params?) — Send a command to a device; an online device acknowledges it in the answer, an offline one queues it — simulated. - flights_search(origin, destination?, date?, include_cancelled?, limit?) — Flights between two airports (IATA codes), optionally on one local date. The seed’s flights span 2026-08-25 to 2026-09-08. - flights_book(flight_id, first_name, last_name, email?, cabin?) — Book a seat on a flight: refuses cancelled, departed and full flights, picks a free seat in the cabin, prices the fare and mints a record locator — simulated. - helpdesk_ticket(ticket_number) — A ticket by number (HD-10042) with its customer, assignee and whole message thread. - helpdesk_reply(ticket_number, body, as?, internal?) — Add a message to a ticket as the agent or the customer; a customer reply reopens a pending ticket — simulated. - social_profile(username, posts?) — A user by username with their counters and latest posts. - echo(message, repeat?) — Answers with the message you send, optionally repeated. - whoami() — On the OAuth-protected endpoint (/mcp/secure): the identity and scopes in your access token. On the open endpoint: says there is none. - add(a, b) — a + b, as text and as structured content checked by an output schema. - slow_operation(seconds?, steps?) — Takes its time, in steps. When the client sends a progressToken and accepts text/event-stream, each step arrives as a progress notification and a log message before the result. - fail(mode?) — Fails, to see how a client shows it: tool_error answers a result with isError: true (the model sees it), protocol_error answers a JSON-RPC error (the client sees it). - generate_image(width?, height?, color?) — A PNG of the size and colour you ask for, as image content. - mixed_content() — One result carrying every content type — text for the user and for the model (annotations), an image, a sound, a resource link and an embedded resource — to see how a client renders each. - large_text(kilobytes?) — A text result of the size you ask for, to see how a client copes with a big answer. Prompts: explore_api(api), investigate_order(order_number), fleet_health_report(site_code), support_reply(ticket_number, tone). Resources: sondahub://{api}/{collection} (first 25 rows), sondahub://{api}/{collection}/page/{page}, sondahub://{api}/{collection}/{id}; completion on template arguments. ## Utilities ### Inspect a request See exactly what arrived: method, path, query, headers and body, parsed. - ANY https://api.sondahub.com/v1/utils/echo — Answers with everything about the request: method, URL, query (repeated keys become arrays), headers, the body parsed as JSON, text, form fields or base64 for binary, and your address. - ANY https://api.sondahub.com/v1/utils/anything/{whatever} — The same as /echo under any path you like. - GET https://api.sondahub.com/v1/utils/get — Echo, but only GET is allowed; other methods answer 405 with an Allow header. - POST https://api.sondahub.com/v1/utils/post — Echo for POST only. Likewise /put, /patch, /delete. - GET https://api.sondahub.com/v1/utils/headers — Just the request headers. - GET https://api.sondahub.com/v1/utils/ip — Your address, and the country and city Cloudflare sees. - GET https://api.sondahub.com/v1/utils/user-agent — Just the User-Agent. - GET https://api.sondahub.com/v1/utils/time — The server clock in ISO, Unix seconds and milliseconds, RFC 2822. - GET https://api.sondahub.com/v1/utils/uuid — A fresh UUID v4. ### Status codes and timing Make the server answer the way you need to test the client. - ANY https://api.sondahub.com/v1/utils/status/{code} — Any status 100–599. A comma list picks one at random per request (/status/200,500,503). 3xx carry a Location, 401 a WWW-Authenticate, 429 and 503 a Retry-After. - GET https://api.sondahub.com/v1/utils/delay/{seconds} — Waits that long (decimals allowed, 10 s at most) before answering. - GET https://api.sondahub.com/v1/utils/slow-random?max=3000 — A random delay up to max milliseconds. - GET https://api.sondahub.com/v1/utils/flaky?rate=0.3&code=500 — Fails with that probability and status — for retries and checks. ### Redirects Chains and single hops, relative and absolute. - GET https://api.sondahub.com/v1/utils/redirect/{n} — n redirects (302, absolute Location) ending at /get. - GET https://api.sondahub.com/v1/utils/relative-redirect/{n} — The same with a relative Location. - GET https://api.sondahub.com/v1/utils/absolute-redirect/{n} — The same with an absolute Location. - GET https://api.sondahub.com/v1/utils/redirect-to?url=/v1/utils/get&status=307 — One redirect to a path on this host with the status you choose (301, 302, 303, 307, 308). Never to another host. ### Cookies Set, read and delete; a cookie jar has something to hold. - GET https://api.sondahub.com/v1/utils/cookies — The cookies the request carried. - GET https://api.sondahub.com/v1/utils/cookies/set?name=value — Sets each query parameter as a cookie (Path=/, a day) and redirects to /cookies. - GET https://api.sondahub.com/v1/utils/cookies/set/{name}/{value} — Sets one cookie from the path. - GET https://api.sondahub.com/v1/utils/cookies/delete?name — Expires the named cookies and redirects to /cookies. ### Bodies, encodings, streams Every shape a response can take. - GET https://api.sondahub.com/v1/utils/json — A sample JSON document with nesting, numbers, unicode and null. - GET https://api.sondahub.com/v1/utils/xml — A sample XML document. - GET https://api.sondahub.com/v1/utils/html — A sample HTML page. - GET https://api.sondahub.com/v1/utils/encoding/utf8 — UTF-8 text from several scripts, with an emoji and a tab. - GET https://api.sondahub.com/v1/utils/gzip — A JSON body compressed with gzip (Content-Encoding: gzip). /deflate likewise. - GET https://api.sondahub.com/v1/utils/bytes/{n} — n random bytes (1 MB at most); ?seed=x makes them repeatable. - GET https://api.sondahub.com/v1/utils/range/{n} — n bytes with Accept-Ranges; send Range: bytes=10-19 for a 206. - GET https://api.sondahub.com/v1/utils/big?rows=5000 — A large JSON array (up to 20,000 rows) to try a viewer on. - GET https://api.sondahub.com/v1/utils/stream/{n}?interval=100 — n lines of JSON (NDJSON), one every interval ms, chunked. - GET https://api.sondahub.com/v1/utils/stream-bytes/{n}?chunk=1024 — n random bytes in chunks. - GET https://api.sondahub.com/v1/utils/drip?numbytes=20&duration=3&delay=0&code=200 — Bytes dripped over the duration, after an optional delay. - GET https://api.sondahub.com/v1/utils/image/svg?text=hello&w=320&h=200&color=f1772c — An SVG with your text. /image/png draws a real PNG (w, h, color); /image picks by your Accept header. ### Tools Small helpers that are handy mid-test. - GET https://api.sondahub.com/v1/utils/base64/{value} — Decodes base64 (standard or URL-safe) to text. - POST https://api.sondahub.com/v1/utils/base64 — Encodes the body you send, standard and URL-safe. - GET https://api.sondahub.com/v1/utils/hash/{algo}?text=sonda — md5, sha1, sha256, sha384, sha512 or crc32 of ?text= — or POST the bytes. - GET https://api.sondahub.com/v1/utils/cache — ETag and Last-Modified; If-None-Match or If-Modified-Since earns a 304. - GET https://api.sondahub.com/v1/utils/cache/{seconds} — Cache-Control: max-age of your choosing. - GET https://api.sondahub.com/v1/utils/etag/{tag} — Your own ETag; If-None-Match gives 304, a wrong If-Match gives 412. - GET https://api.sondahub.com/v1/utils/response-headers?X-Powered-By=sondahub — Each query parameter comes back as a response header. ### Forms and uploads Multipart and urlencoded, parsed and described. - POST https://api.sondahub.com/v1/utils/forms/post — Fields and files, with each file’s size, type, SHA-256 and MD5 (1 MB in all). /upload is the same route. (multipart/form-data or application/x-www-form-urlencoded) ### Streams and sockets Server-Sent Events and WebSockets with nothing to set up. - GET https://api.sondahub.com/v1/utils/sse?count=10&interval=1000 — A clock over SSE: count ticks, one per interval, with ids, a second event name every fifth tick, and Last-Event-ID resumption. - WS wss://api.sondahub.com/v1/utils/ws — Echo: every frame you send comes straight back, text or binary. ### Webhooks Add X-Sondahub-Webhook: to any write and each record it creates, changes or deletes is POSTed there, signed (Standard Webhooks, Stripe or GitHub style; playground secret whsec_c29uZGFodWItcGxheWdyb3VuZC13ZWJob29rLWtleSE=). - GET https://api.sondahub.com/v1/utils/webhooks — How the signing works, per style, and the headers that steer delivery. - POST https://api.sondahub.com/v1/utils/webhooks/send — Send one sample event to a URL now, and see what was sent: body, signature headers, the signed string, and how your endpoint answered. ({"url", "style", "secret", "type", "data"}) - POST https://api.sondahub.com/v1/utils/webhooks/verify — Check a signature the way a receiver should: valid or not, why, and the signature that was expected. ({"style", "secret", "body", "headers"}) ### Authentication Every scheme, checked for real. User sonda / probe; API key sonda-probe-key; client sonda / probe-secret; AWS AKIASONDAHUB000001 / probe-secret (us-east-1, execute-api); JWT secret probe-secret. - GET https://api.sondahub.com/v1/utils/auth/basic — HTTP Basic with sonda / probe. /auth/basic/{user}/{pass} takes any pair you name. Wrong or missing answers 401 with WWW-Authenticate. - GET https://api.sondahub.com/v1/utils/auth/hidden-basic/{user}/{pass} — Basic, but a failure answers 404 as if the route did not exist. - GET https://api.sondahub.com/v1/utils/auth/bearer — Any non-empty bearer token passes. /auth/bearer/{token} wants exactly that token. - GET https://api.sondahub.com/v1/utils/auth/apikey — X-API-Key: sonda-probe-key (or ?api_key=, or Authorization: ApiKey …). /auth/apikey/{key} wants that key instead. - GET https://api.sondahub.com/v1/utils/auth/digest — HTTP Digest (RFC 7616) with sonda / probe: ?algorithm=MD5|MD5-sess|SHA-256|SHA-256-sess and ?qop=auth|auth-int choose the challenge; /auth/digest/{user}/{pass} takes any pair. A failed check says which part did not match. - ANY https://api.sondahub.com/v1/utils/auth/sigv4 — AWS Signature Version 4, verified: access key AKIASONDAHUB000001, secret probe-secret, region us-east-1, service execute-api. A mismatch answers 403 with the canonical request and string-to-sign the server built, to compare with yours. ### OAuth 2.0 and OpenID Connect A small real server: client sonda / probe-secret (or register your own), user sonda / probe or anyone in the Identity directory (user_name / probe). Access tokens are RS256 JWTs you can check against the JWKS; discovery at /.well-known/openid-configuration. - POST https://api.sondahub.com/v1/utils/oauth/token — grant_type=client_credentials | password | authorization_code | refresh_token. Client as HTTP Basic or client_id/client_secret in the form body. Scope openid adds an id_token; resource= (RFC 8707) becomes the token audience. (application/x-www-form-urlencoded) - GET https://api.sondahub.com/v1/utils/oauth/authorize — The consent screen for response_type=code (PKCE S256 or plain supported). Sonda opens it in the browser and receives the code on its 127.0.0.1 redirect. ?auto=1 skips the screen and allows. - GET https://api.sondahub.com/v1/utils/oauth/protected — Needs Authorization: Bearer . ?scope=write demands that scope (403 insufficient_scope otherwise). - GET https://api.sondahub.com/v1/utils/oauth/userinfo — The OIDC userinfo for the bearer token. - POST https://api.sondahub.com/v1/utils/oauth/introspect — token=… answers active and the claims. (application/x-www-form-urlencoded) - POST https://api.sondahub.com/v1/utils/oauth/revoke — Answers 200; the hub keeps no token state, so the token lives until it expires. - POST https://api.sondahub.com/v1/utils/oauth/register — Dynamic Client Registration (RFC 7591): POST client metadata as JSON, get a client_id (and a secret unless token_endpoint_auth_method is none). Nothing is stored: the client_id carries its own signed registration. ({"client_name", "redirect_uris", "grant_types", "token_endpoint_auth_method"}) - GET https://api.sondahub.com/v1/utils/oauth/register/{client_id} — Read a registration back (RFC 7592). - GET https://api.sondahub.com/.well-known/openid-configuration — Discovery document, with the playground client in it. - GET https://api.sondahub.com/.well-known/oauth-authorization-server — RFC 8414 authorization-server metadata (the same facts). - GET https://api.sondahub.com/.well-known/oauth-protected-resource/mcp/secure — RFC 9728 protected-resource metadata for the OAuth-protected MCP endpoint. - GET https://api.sondahub.com/.well-known/jwks.json — The RS256 public key. ### JWT Mint, decode, verify and use tokens. HS256 secret probe-secret; RS256 keys published (the private one too — it is a playground). - GET https://api.sondahub.com/v1/utils/jwt/issue?sub=alice&role=admin&alg=HS256&expires_in=3600 — A token with the query parameters as claims. POST {"alg","expires_in","claims":{…}} for anything richer. - POST https://api.sondahub.com/v1/utils/jwt/verify — {"token": …} (or ?token=, or a Bearer header): valid or not, why, and the claims. - GET https://api.sondahub.com/v1/utils/jwt/decode?token=… — Header and payload, nothing checked. - GET https://api.sondahub.com/v1/utils/jwt/protected — Needs a valid Bearer JWT signed with either key. Sign your own and it passes. - GET https://api.sondahub.com/v1/utils/jwt/keys — The HS256 secret and the RS256 private JWK. ## Playground credentials (public on purpose; they protect nothing) Basic/Digest user sonda / probe; API key sonda-probe-key; OAuth client sonda / probe-secret (or register one); directory users {user_name} / probe; SCIM token sonda-scim-token; webhook secret whsec_c29uZGFodWItcGxheWdyb3VuZC13ZWJob29rLWtleSE=; JWT HS256 secret probe-secret; AWS SigV4 AKIASONDAHUB000001 / probe-secret (us-east-1, execute-api); JWKS https://api.sondahub.com/.well-known/jwks.json; OIDC discovery https://api.sondahub.com/.well-known/openid-configuration. Built by LockFlare (https://lockflare.com) as the playground for LockFlare Sonda, an API client: https://lockflare.com/sonda