sondahub

Utilities

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.
A teapot
curl -i https://api.sondahub.com/v1/utils/status/418
Sometimes broken
curl -i "https://api.sondahub.com/v1/utils/flaky?rate=0.5"

Redirects

Chains and single hops, relative and absolute.

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.
Ranges
curl -i -H "Range: bytes=10-19" https://api.sondahub.com/v1/utils/range/100
A drawn PNG
curl -o hub.png "https://api.sondahub.com/v1/utils/image/png?w=200&h=120&color=2f6df6"

Tools

Small helpers that are handy mid-test.

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)
Upload
curl -F "name=Ada" -F "[email protected]" https://api.sondahub.com/v1/utils/forms/post

Streams and sockets

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.
Ten ticks
curl -N "https://api.sondahub.com/v1/utils/sse?count=10&interval=1000"

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/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.
Basic
curl -u sonda:probe https://api.sondahub.com/v1/utils/auth/basic
Digest, SHA-256
curl --digest -u sonda:probe "https://api.sondahub.com/v1/utils/auth/digest?algorithm=SHA-256"
API key
curl -H "X-API-Key: sonda-probe-key" https://api.sondahub.com/v1/utils/auth/apikey
AWS SigV4 (curl 7.75+)
curl --aws-sigv4 "aws:amz:us-east-1:execute-api" --user "AKIASONDAHUB000001:probe-secret" https://api.sondahub.com/v1/utils/auth/sigv4

OAuth 2.0 and OpenID Connect

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.
GET/v1/utils/oauth/protectedNeeds Authorization: Bearer <access_token>. ?scope=write demands that scope (403 insufficient_scope otherwise).
GET/v1/utils/oauth/userinfoThe OIDC userinfo for the bearer token.
POST/v1/utils/oauth/introspecttoken=… answers active and the claims. (application/x-www-form-urlencoded)
POST/v1/utils/oauth/revokeAnswers 200; the hub keeps no token state, so the token lives until it expires.
GET/.well-known/openid-configurationDiscovery document, with the playground client in it.
GET/.well-known/jwks.jsonThe RS256 public key.
Client credentials
curl -u sonda:probe-secret -d grant_type=client_credentials -d scope=read https://api.sondahub.com/v1/utils/oauth/token
Password grant with an id_token
curl -d grant_type=password -d username=sonda -d password=probe -d client_id=sonda -d client_secret=probe-secret -d "scope=openid read" https://api.sondahub.com/v1/utils/oauth/token
Use the token
curl -H "Authorization: Bearer ACCESS_TOKEN" https://api.sondahub.com/v1/utils/oauth/protected

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.
Mint and use
curl "https://api.sondahub.com/v1/utils/jwt/issue?sub=alice&role=admin"
curl -H "Authorization: Bearer TOKEN" https://api.sondahub.com/v1/utils/jwt/protected