Everything a client has to get right that is not a business API: seeing what you sent, every status code, slow answers, redirects, cookies, compression, streams, uploads — and every authentication scheme, checked for real, with an OAuth 2 server and a JWT issuer.
Base URL https://api.sondahub.com/v1/utils. Credentials are public on purpose; they prove a flow works and protect nothing.
Username / password
sonda / probe
API key
sonda-probe-key
OAuth client
sonda / probe-secret
JWT HS256 secret
probe-secret
AWS access key
AKIASONDAHUB000001
AWS secret key
probe-secret
AWS region / service
us-east-1 / execute-api
JWKS
/.well-known/jwks.json
Inspect a request
See exactly what arrived: method, path, query, headers and body, parsed.
ANY/v1/utils/echoAnswers 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/v1/utils/anything/{whatever}The same as /echo under any path you like.
GET/v1/utils/getEcho, but only GET is allowed; other methods answer 405 with an Allow header.
POST/v1/utils/postEcho for POST only. Likewise /put, /patch, /delete.
GET/v1/utils/headersJust the request headers.
GET/v1/utils/ipYour address, and the country and city Cloudflare sees.
GET/v1/utils/user-agentJust the User-Agent.
GET/v1/utils/timeThe server clock in ISO, Unix seconds and milliseconds, RFC 2822.
GET/v1/utils/uuidA fresh UUID v4.
Echo a POST
curl -X POST "https://api.sondahub.com/v1/utils/echo?a=1&a=2" -H "Content-Type: application/json" -d '{"hello":"sonda"}'
Your headers
curl https://api.sondahub.com/v1/utils/headers
Status codes and timing
Make the server answer the way you need to test the client.
ANY/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/v1/utils/delay/{seconds}Waits that long (decimals allowed, 10 s at most) before answering.
GET/v1/utils/slow-random?max=3000A random delay up to max milliseconds.
GET/v1/utils/flaky?rate=0.3&code=500Fails with that probability and status — for retries and checks.
GET/v1/utils/redirect/{n}n redirects (302, absolute Location) ending at /get.
GET/v1/utils/relative-redirect/{n}The same with a relative Location.
GET/v1/utils/absolute-redirect/{n}The same with an absolute Location.
GET/v1/utils/redirect-to?url=/v1/utils/get&status=307One 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/v1/utils/cookiesThe cookies the request carried.
GET/v1/utils/cookies/set?name=valueSets each query parameter as a cookie (Path=/, a day) and redirects to /cookies.
GET/v1/utils/cookies/set/{name}/{value}Sets one cookie from the path.
GET/v1/utils/cookies/delete?nameExpires the named cookies and redirects to /cookies.
Set, then read
curl -c jar -b jar -L "https://api.sondahub.com/v1/utils/cookies/set?flavor=chocolate&count=2"
Bodies, encodings, streams
Every shape a response can take.
GET/v1/utils/jsonA sample JSON document with nesting, numbers, unicode and null.
GET/v1/utils/xmlA sample XML document.
GET/v1/utils/htmlA sample HTML page.
GET/v1/utils/encoding/utf8UTF-8 text from several scripts, with an emoji and a tab.
GET/v1/utils/gzipA JSON body compressed with gzip (Content-Encoding: gzip). /deflate likewise.
GET/v1/utils/bytes/{n}n random bytes (1 MB at most); ?seed=x makes them repeatable.
GET/v1/utils/range/{n}n bytes with Accept-Ranges; send Range: bytes=10-19 for a 206.
GET/v1/utils/big?rows=5000A large JSON array (up to 20,000 rows) to try a viewer on.
GET/v1/utils/stream/{n}?interval=100n lines of JSON (NDJSON), one every interval ms, chunked.
GET/v1/utils/stream-bytes/{n}?chunk=1024n random bytes in chunks.
GET/v1/utils/drip?numbytes=20&duration=3&delay=0&code=200Bytes dripped over the duration, after an optional delay.
GET/v1/utils/image/svg?text=hello&w=320&h=200&color=f1772cAn SVG with your text. /image/png draws a real PNG (w, h, color); /image picks by your Accept header.
GET/v1/utils/base64/{value}Decodes base64 (standard or URL-safe) to text.
POST/v1/utils/base64Encodes the body you send, standard and URL-safe.
GET/v1/utils/hash/{algo}?text=sondamd5, sha1, sha256, sha384, sha512 or crc32 of ?text= — or POST the bytes.
GET/v1/utils/cacheETag and Last-Modified; If-None-Match or If-Modified-Since earns a 304.
GET/v1/utils/cache/{seconds}Cache-Control: max-age of your choosing.
GET/v1/utils/etag/{tag}Your own ETag; If-None-Match gives 304, a wrong If-Match gives 412.
GET/v1/utils/response-headers?X-Powered-By=sondahubEach query parameter comes back as a response header.
Forms and uploads
Multipart and urlencoded, parsed and described.
POST/v1/utils/forms/postFields 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)
Server-Sent Events and WebSockets with nothing to set up.
GET/v1/utils/sse?count=10&interval=1000A clock over SSE: count ticks, one per interval, with ids, a second event name every fifth tick, and Last-Event-ID resumption.
WS/v1/utils/wsEcho: every frame you send comes straight back, text or binary.
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/v1/utils/auth/basicHTTP Basic with sonda / probe. /auth/basic/{user}/{pass} takes any pair you name. Wrong or missing answers 401 with WWW-Authenticate.
GET/v1/utils/auth/hidden-basic/{user}/{pass}Basic, but a failure answers 404 as if the route did not exist.
GET/v1/utils/auth/bearerAny non-empty bearer token passes. /auth/bearer/{token} wants exactly that token.
GET/v1/utils/auth/apikeyX-API-Key: sonda-probe-key (or ?api_key=, or Authorization: ApiKey …). /auth/apikey/{key} wants that key instead.
GET/v1/utils/auth/digestHTTP 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/v1/utils/auth/sigv4AWS 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.
A small real server: client sonda / probe-secret, user sonda / probe. Access tokens are RS256 JWTs you can check against the JWKS; discovery at /.well-known/openid-configuration.
POST/v1/utils/oauth/tokengrant_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. (application/x-www-form-urlencoded)
GET/v1/utils/oauth/authorizeThe 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.
In Sonda’s auth editor, OAuth 2 with the authorization-code grant: authorization URL https://api.sondahub.com/v1/utils/oauth/authorize, token URL https://api.sondahub.com/v1/utils/oauth/token, client sonda / probe-secret, any scope, PKCE on or off. Sonda opens the consent screen in the browser and receives the code on its 127.0.0.1 redirect.
JWT
Mint, decode, verify and use tokens. HS256 secret probe-secret; RS256 keys published (the private one too — it is a playground).
GET/v1/utils/jwt/issue?sub=alice&role=admin&alg=HS256&expires_in=3600A token with the query parameters as claims. POST {"alg","expires_in","claims":{…}} for anything richer.
POST/v1/utils/jwt/verify{"token": …} (or ?token=, or a Bearer header): valid or not, why, and the claims.
GET/v1/utils/jwt/decode?token=…Header and payload, nothing checked.
GET/v1/utils/jwt/protectedNeeds a valid Bearer JWT signed with either key. Sign your own and it passes.
GET/v1/utils/jwt/keysThe HS256 secret and the RS256 private JWK.