Rustybinv0.6.0

HTTP stub service for API and AI gateway demos

Presenting a gateway demo?

The web console shows exactly what the gateway sent upstream (live traffic, headers, bodies, side by side compare) and drives the mocks: AI playground, MCP inspector, A2A client, request bins, chaos controls and token lab. Works offline.

Open the console
Export collections: PostmanInsomniaBrunocURL.httpHurlk6HAR

Echo & Reflection

ANY
/echoEcho back the full request (method, headers, query, body)
ANY
/echo/{*path}Echo with an arbitrary sub-path
ANY
/anythingAlias for /echo
ANY
/anything/{*path}Alias for /echo/{*path}

Status Codes

ANY
/status/{code}Respond with any HTTP status code (200-599), or a weighted random choice
`/status/200,500` picks uniformly, `/status/200:0.9,500:0.1` by weight (up to 20 codes). 1xx codes return 400 (they cannot be final responses). Redirects carry `Location: /echo`, 401 `WWW-Authenticate`, 407 `Proxy-Authenticate`, 429/503 `Retry-After`.

Response Shaping

ANY
/delay/{ms}Wait, then respond: milliseconds (1500), `250ms` or seconds (`1.5s`); ?jitter=true adds +-20%
Maximum 60 s (10 s in public mode), jitter included: larger values return 400.
GET
/cache/{ttl}Cache-Control, ETag and Last-Modified; 304 on If-None-Match / If-Modified-Since
If-None-Match (lists, weak tags, `*`) takes precedence over If-Modified-Since. JSON and XML representations have different ETags (Vary: Accept).
GET
/response-headersQuery parameters become response headers (hop-by-hop / framing headers refused)
GET
/gzipgzip-compressed JSON echo (gzipped: true)
GET
/deflatedeflate (zlib) compressed JSON echo (deflated: true)
GET
/brotliBrotli-compressed JSON echo (brotli: true)
GET
/zstdZstandard-compressed JSON echo (zstd: true)
GET
/encoding/utf8UTF-8 sample page (many scripts, symbols, emoji)
GET
/range/{n}n bytes supporting Range requests (206, 416, ETag, If-Range)
Body is abc...z repeated, n up to 102400 (public 10240). Single ranges only (bytes=a-b, bytes=a-, bytes=-n): 206 with Content-Range; unsatisfiable or malformed ranges are 416 with Content-Range: bytes */n; multiple ranges and unknown units get the full 200 body.
GET
/bytes/{n}n random bytes (seed for deterministic output)
n is capped at 102400 (public 10240); X-Rustybin-Capped reports clamping. ?seed= makes the output reproducible.
GET
/stream/{n}n JSON lines streamed (chunked)
Each line echoes url, args, headers and origin with an id. n is capped at 100 (public 20).
GET
/stream-bytes/{n}n random bytes streamed in chunks
?chunk_size= (default 10240), ?seed=. Same cap as /bytes.
GET
/dripDrip bytes over a duration (slow body)
?duration= seconds (default 2), ?numbytes= (default 10, max 10240; public 1024), ?code= status, ?delay= seconds before headers. Duration and delay are capped by the instance delay limit.
GET
/links/{n}Redirect to /links/{n}/0
GET
/links/{n}/{offset}HTML page with n links (max 200)
GET
/base64/{value}Decode a base64 (standard or URL-safe) value

Streaming (SSE)

GETSSE
/sseNumbered event stream with resume (Last-Event-ID) and heartbeats
Query: count (default 10, max 1000; public 100), interval_ms (default 1000, max 60000; public 10000), event (event name), retry (reconnection delay sent with the first event), mode=json|text, heartbeat_ms (comment heartbeat when idle, default 15000, 0 disables), last_event_id. Event ids are sequence numbers; Last-Event-ID: n resumes at n+1. Streams end after 10 minutes (public 2 minutes).
GETPOSTSSE
/sse/chatProvider-neutral streamed chat text, one word per event, ending with [DONE]
GET ?prompt=&max_tokens=&delay_ms= or POST {"prompt", "max_tokens", "delay_ms"}. Each event is `data: {"id","object":"chat.chunk","index","delta","finish_reason"}`; the last chunk carries finish_reason (stop or length) and usage, then `data: [DONE]`. delay_ms defaults to 50 (max 2000; public 1000), max_tokens to 500 (public 200).

Redirects & Cookies

GET
/redirect/{n}Chain of n relative 302 redirects
GET
/cookiesRequest cookies as JSON
GET
/cookies/setSet cookies from query parameters, then 302 to /cookies
Attributes: `_path` (default /), `_domain`, `_secure`, `_httponly`, `_samesite` (Strict, Lax, None; None forces Secure), `_maxage`. Names must be tokens; values are percent-encoded where needed.
GET
/cookies/set/{name}/{value}Set a single cookie (Path=/)
GET
/cookies/deleteExpire the cookies named in the query string (_path / _domain must match how they were set)
ANY
/redirect-toRedirect to a relative path or this host only (no open redirect)
?url= must be a path starting with / (not //) or an http(s) URL whose host:port equals the request Host; anything else is 400. ?status_code= 300, 301, 302 (default), 303, 307 or 308.
GET
/absolute-redirect/{n}Chain of n absolute 302 redirects on the same host (max 10)

Info & Random

GET
/ipClient IP address (IPv4 and IPv6)
GET
/ip/v4Client IPv4 address
GET
/ip/v6Client IPv6 address
GET
/dateCurrent date (UTC)
GET
/date/{*timezone}Current date in an IANA timezone
GET
/timeCurrent time, ISO 8601 (UTC)
GET
/time/{*timezone}Current time in an IANA timezone
GET
/uuidRandom UUID v4
GET
/guuidRandom braced GUID
GET
/randomBundle of random values
GET
/random/intRandom signed integer
GET
/random/int/{lower}/{upper}Random integer in [lower, upper]
GET
/random/uintRandom unsigned integer
GET
/random/lorem-ipsumOne paragraph of lorem ipsum
GET
/random/lorem-ipsum/{count}count paragraphs of lorem ipsum
GET
/imageImage in the format chosen by Accept (webp, svg, jpeg, png, gif; 406 otherwise)
GET
/image/png16x16 PNG image
GET
/image/jpeg8x8 JPEG image
GET
/image/gif8x8 GIF image
GET
/image/webp8x8 lossless WebP image
GET
/image/svgSVG image

Auth: Basic & API Key

ANY
/auth/basic-authHTTP Basic auth (default user basic, password password)
Default credentials: user `basic`, password `password`. 401 responses carry `WWW-Authenticate: Basic realm="rustybin"`. Comparisons are constant time.
ANY
/auth/basic-auth/{username}/{password}HTTP Basic auth with credentials from the path
ANY
/auth/api-keyAPI key in a header (default apikey: my-key)
Default: header `apikey` with value `my-key` (header name matched case-insensitively, value compared in constant time).
ANY
/auth/api-key/{header_name}/{key_value}API key with header name and value from the path

Auth: HMAC

ANY
/auth/hmacValidate an hmac-auth / draft-cavage signature (default alice / secret)
Authorization (or Proxy-Authorization): hmac username="alice", algorithm="hmac-sha256", headers="date request-line", signature="base64(HMAC(secret, signing string))". `Signature keyId=...` also works. Algorithms hmac-sha1/sha256/sha384/sha512. Date or X-Date required within ?clock_skew= seconds (default 300, 0 disables); a signed digest header (SHA-256=base64) is checked against the body.
ANY
/auth/hmac/{username}/{secret}HMAC validation with username and secret from the path

Auth: JWT

ANY
/auth/jwtValidate a Bearer JWT (HS256 demo secret or RS256 IdP key, exp/nbf, optional ?iss= ?aud=)
Verifies the signature (alg from the header: HS256 with the demo secret `rustybin-demo-secret-do-not-use-in-production`, RS256 with the key at /oauth/jwks; alg none and other algorithms are rejected), requires exp, checks nbf, and optionally ?iss= and ?aud=. 401 with WWW-Authenticate on failure.
ANY
/auth/jwt/decodeDecode a Bearer JWT WITHOUT validation (shows what the gateway forwarded)
Returns the header and claims of any structurally valid JWT. Nothing is verified: use it to inspect tokens a gateway injected or forwarded.
ANY
/auth/jwt/exchangeExchange a valid JWT for a new HS256-signed token (same checks as /auth/jwt)

Auth: OIDC Provider

GET
/.well-known/openid-configurationOIDC discovery document
Issuer is derived from Host (and X-Forwarded-Proto / X-Forwarded-Host / X-Forwarded-Prefix only when RUSTYBIN_TRUST_FORWARD=true).
GET
/.well-known/oauth-authorization-serverOAuth 2.0 authorization server metadata (RFC 8414)
POST
/oauth/tokenToken endpoint (authorization_code + PKCE, refresh_token, client_credentials, password, token exchange)
Clients: rustybin / secret (confidential; client_secret_basic or client_secret_post) and rustybin-public (public, PKCE). Users for the password grant and login form: demo/demo, alice/alice, bob/bob. `resource` (RFC 8707) sets the access token audience (default rustybin). Refresh tokens rotate. Token exchange (RFC 8693) only accepts subject tokens signed by this server (RS256 IdP key or HS256 demo secret).
GET
/oauth/jwksRS256 public key in JWK Set format
GETPOST
/oauth/authorizeAuthorization code flow with a demo login form (PKCE, nonce, resource)
redirect_uri must be an absolute http(s) URL: any for the demo clients, an exact registered one for dynamically registered clients. The public client rustybin-public must send code_challenge (S256 or plain). Codes are single use and expire after 5 minutes.
GETPOST
/oauth/userinfoUser claims for a Bearer access token (scope openid)
POST
/oauth/introspectToken introspection (RFC 7662, client authentication required)
POST
/oauth/revokeToken revocation (RFC 7009) for refresh and access tokens
POST
/oauth/registerDynamic client registration (RFC 7591, used by MCP clients)
Registered clients are kept for 24 hours in a bounded registry (1000 clients, 200 in public mode). token_endpoint_auth_method none registers a public client (PKCE required).

Auth: mTLS

ANY
/auth/mtlsValidate the client certificate (TLS peer certificate, or the RUSTYBIN_MTLS_IN_HEADER header)
On the HTTPS listener the verified TLS client certificate is used. Otherwise, when RUSTYBIN_MTLS_IN_HEADER is set, the certificate forwarded by the gateway in that header (URL-encoded PEM, PEM or base64 DER) must be issued by the demo CA and currently valid.
GET
/auth/mtls/get-client-certDownload the demo client certificate and key
GET
/auth/mtls/get-ca-certDownload the demo CA certificate (persisted across restarts when the cert dir is writable)

AI: OpenAI-compatible

POST
/ai/openai/v1/chat/completionsChat completions (SSE streaming with stream=true, tools, structured output, n)
Content may be a string, null or an array of parts (text, image_url, input_audio, file). Tools produce tool_calls when the user text mentions the tool; response_format json_schema returns schema-valid JSON. Mode: X-Rustybin-Mode or model name (rustybin-echo, rustybin-scripted, rustybin-random).
POST
/ai/v1/chat/completionsChat completions (SSE streaming with stream=true, tools, structured output, n)
Alias of the /ai/openai/v1 route (kept for backwards compatibility).
POST
/ai/openai/v1/completionsLegacy text completions (stream, echo, suffix, n)
prompt may be a string, an array of strings or token arrays.
POST
/ai/v1/completionsLegacy text completions (stream, echo, suffix, n)
Alias of the /ai/openai/v1 route (kept for backwards compatibility).
POST
/ai/openai/v1/embeddingsDeterministic bag-of-words embeddings (dimensions, base64)
Same input gives the same vector; case-insensitive; reordered words stay close (cosine about 0.9). Default 1536 dimensions (3072 for *large* models), `dimensions` overrides, `encoding_format: base64` returns little-endian f32.
POST
/ai/v1/embeddingsDeterministic bag-of-words embeddings (dimensions, base64)
Alias of the /ai/openai/v1 route (kept for backwards compatibility).
GET
/ai/openai/v1/modelsList available models
GET
/ai/v1/modelsList available models
Alias of the /ai/openai/v1 route (kept for backwards compatibility).
GET
/ai/openai/v1/models/{model}Retrieve a model (404 model_not_found for unknown ids)
GET
/ai/v1/models/{model}Retrieve a model (404 model_not_found for unknown ids)
Alias of the /ai/openai/v1 route (kept for backwards compatibility).
POST
/ai/openai/v1/moderationsModeration (keyword-based categories, deterministic scores)
Flags whole words such as hate, idiot, kill, jailbreak, suicide (see the README table).
POST
/ai/v1/moderationsModeration (keyword-based categories, deterministic scores)
Alias of the /ai/openai/v1 route (kept for backwards compatibility).
POST
/ai/openai/v1/images/generationsImage generation (tiny valid PNG as b64_json or a URL to /image/png)
POST
/ai/v1/images/generationsImage generation (tiny valid PNG as b64_json or a URL to /image/png)
Alias of the /ai/openai/v1 route (kept for backwards compatibility).
POST
/ai/openai/v1/audio/transcriptionsAudio transcription (multipart; json, text, srt, vtt, verbose_json)
Returns a fixed transcript for any uploaded file.
POST
/ai/v1/audio/transcriptionsAudio transcription (multipart; json, text, srt, vtt, verbose_json)
Alias of the /ai/openai/v1 route (kept for backwards compatibility).
POST
/ai/openai/v1/responsesResponses API (output items, function calls, native SSE events with stream=true)
input may be a string or a list of message / function_call / function_call_output items; instructions is the system prompt; text.format json_schema gives schema-valid JSON; max_output_tokens truncates (status incomplete).
POST
/ai/v1/responsesResponses API
Alias of /ai/openai/v1/responses.
POST
/ai/azure/openai/deployments/{deployment}/chat/completionsAzure OpenAI chat completions (api-version required, model = deployment)
Credential header: api-key. Responses include prompt_filter_results and content_filter_results; X-Rustybin-Fail: prompt_filter returns Azure's content_filter error.
POST
/ai/azure/openai/deployments/{deployment}/completionsAzure OpenAI legacy completions
POST
/ai/azure/openai/deployments/{deployment}/embeddingsAzure OpenAI embeddings

AI: Anthropic-compatible

POST
/ai/anthropic/v1/messagesMessages API (tools, prompt caching usage, native SSE event stream with stream=true)
Credential: x-api-key + anthropic-version (enforced with X-Rustybin-Require-Auth). max_tokens is required (400 otherwise). X-Rustybin-Fail: 529 returns overloaded_error.
POST
/ai/anthropic/v1/messages/count_tokensCount input tokens

AI: Mock LLM

GET
/ai/gemini/v1beta/modelsGemini: list models
GETPOST
/ai/gemini/v1beta/models/{*rest}Gemini: {model}:generateContent, :streamGenerateContent (?alt=sse), :countTokens, :embedContent, :batchEmbedContents
Credential: x-goog-api-key or ?key=. functionDeclarations produce functionCall parts; generationConfig.responseSchema gives schema-valid JSON; usageMetadata on every response. GET models/{model} describes the model.
POST
/ai/bedrock/model/{model_id}/converseBedrock Converse
Credential: SigV4 Authorization (structural check) or Bedrock API key bearer, enforced with X-Rustybin-Require-Auth. toolConfig produces toolUse blocks.
POST
/ai/bedrock/model/{model_id}/converse-streamBedrock ConverseStream (binary AWS event stream with CRC32 framing)
POST
/ai/bedrock/model/{model_id}/invokeBedrock InvokeModel (Anthropic body, Titan text/embeddings, Llama prompt)
POST
/ai/bedrock/model/{model_id}/invoke-with-response-streamBedrock InvokeModelWithResponseStream (event stream of base64 chunks)
POST
/ai/ollama/api/chatOllama chat (NDJSON stream by default; tools, format)
POST
/ai/ollama/api/generateOllama generate (NDJSON stream by default)
GET
/ai/ollama/api/tagsOllama local models
POST
/ai/ollama/api/embedOllama embeddings (768 dimensions by default)
POST
/ai/ollama/api/embeddingsOllama legacy embeddings
POST
/ai/cohere/v2/rerankRerank (deterministic relevance scores, top_n)
POST
/ai/cohere/v2/embedCohere embed (float, int8, uint8, base64)
POST
/ai/v1/rerankGeneric rerank alias (same as /ai/cohere/v2/rerank)
GET
/ai/requestsRecent mock LLM exchanges (newest first)
Public mode only lists the caller's own session (X-Rustybin-Session or client IP). ?limit=, ?session=.
GET
/ai/requests/{id}What the upstream received for one AI request
Look up by the X-Rustybin-Request-Id response header: headers (credentials redacted), body, normalised prompt, provider, model, mode and computed tokens.

AI: Guardrails

POST
/guardrails/azure/contentsafety/{operation}Azure AI Content Safety style text:analyze and text:shieldPrompt
operation is `text:analyze` ({text, categories, blocklistNames, haltOnBlocklistHit, outputType} -> categoriesAnalysis [{category, severity}] for Hate, SelfHarm, Sexual, Violence plus blocklistsMatch; FourSeverityLevels reports 0/2/4/6) or `text:shieldPrompt` ({userPrompt, documents} -> userPromptAnalysis.attackDetected, documentsAnalysis[].attackDetected). Any api-version and key are accepted; every requested blocklist name contains the demo items. Keyword lists: see /guardrails/check.
POST
/guardrails/bedrock/guardrail/{id}/version/{version}/applyAWS Bedrock ApplyGuardrail style assessment (block, anonymize PII)
Body {source: INPUT|OUTPUT, content: [{text: {text, qualifiers}}], outputScope}. Harmful keywords, prompt attacks (INPUT only) and blocklist words block with GUARDRAIL_INTERVENED and a canned message; PII alone is anonymized ({EMAIL}, {PHONE}, {US_SOCIAL_SECURITY_NUMBER}, {CREDIT_DEBIT_CARD_NUMBER}, {IP_ADDRESS}) in outputs. Guardrail ids containing `block-pii` block PII instead. Assessments include contentPolicy, wordPolicy, sensitiveInformationPolicy and invocationMetrics. Requests are not SigV4-checked.
POST
/guardrails/checkGeneric guardrail check: flagged, categories, jailbreak, PII spans, redacted text
Keywords (whole words, case-insensitive; severity on the 0-7 scale): Hate: hateful 2, bigot 3, racist 4, subhuman 6. SelfHarm: self-harm 4, hurt myself 4, suicide 6, kill myself 6. Sexual: explicit 2, nsfw 4, porn 6. Violence: fight 2, weapon 4, kill 4, bomb 6, murder 6. Jailbreak phrases: ignore (all) previous instructions, ignore the above, ignore your instructions, disregard previous/your instructions, forget your instructions, you are now DAN, do anything now, developer mode, jailbreak, pretend you have no restrictions, reveal/print your system prompt, bypass your safety. Blocklist demo-blocklist: badword, blockedterm, forbidden phrase. PII: email, phone, US SSN, credit card (Luhn), IPv4/IPv6.
POST
/guardrails/pii/redactRedact emails, phone numbers, SSNs, credit cards (Luhn) and IPs
JSON {text, mode: label|mask, types: [EMAIL, PHONE, SSN, CREDIT_CARD, IP_ADDRESS]} returns {redacted_text, count, entities [{type, text, start, end}]} with character offsets; a text/plain body returns the redacted text (?mode=, ?types= comma separated).

MCP Server

GET
/mcp-registryMock MCP registry: what it is and where its API lives
GET
/mcp-registry/v0.1/serversMock MCP registry: list servers (one record per version; cursor, limit, search, updated_since, version, include_deleted)
This instance's MCP servers (io.rustybin/...), the config file's registry.servers and the published entries, ordered by name then version. The registry.scenario setting breaks it on purpose (empty, large, flaky, slow, stale, invalid, auth_fail).
GET
/mcp-registry/v0.1/servers/{serverName}/versionsMock MCP registry: every version of a server (the name URL-encoded)
GET
/mcp-registry/v0.1/servers/{serverName}/versions/{version}Mock MCP registry: one version of a server (latest for the latest)
PATCH
/mcp-registry/v0.1/servers/{serverName}/versions/{version}/statusMock MCP registry: change the status of a published version (active, deprecated, deleted; registry token)
PATCH
/mcp-registry/v0.1/servers/{serverName}/statusMock MCP registry: change the status of every published version of a server (registry token)
POST
/mcp-registry/v0.1/publishMock MCP registry: publish a server.json (Authorization: Bearer <token from auth/*>)
POST
/mcp-registry/v0.1/validateMock MCP registry: validate a server.json against the vendored schema ({valid, issues})
POST
/mcp-registry/v0.1/auth/{method}Mock MCP registry: token exchange stubs (github-at, github-oidc, oidc, dns, http, none) that always succeed, or fail in the auth_fail scenario
GET
/mcp-registry/v0.1/healthMock MCP registry: health
GET
/mcp-registry/v0.1/pingMock MCP registry: ping
GET
/mcp-registry/v0.1/versionMock MCP registry: version
ANY
/mcp-registry/v0/{*path}Mock MCP registry: the same API under /v0
POSTGETDELETE
/mcpMCP Streamable HTTP server (2026-07-28 stateless + 2025-xx sessions)
POST one JSON-RPC message. 2026-07-28: no initialize; every request carries `_meta` io.modelcontextprotocol/protocolVersion + clientCapabilities and the MCP-Protocol-Version, Mcp-Method and Mcp-Name headers (validated against the body, -32020 on mismatch); `server/discover`, `subscriptions/listen`, multi round-trip elicitation/sampling, ttlMs/cacheScope on list results. 2025-11-25/2025-06-18/2025-03-26: `initialize` returns Mcp-Session-Id (required afterwards), GET opens the server stream, DELETE ends the session. Responses are JSON or SSE (progress, logs). `?page_size=N` paginates list results.
POSTGETDELETE
/mcp/protectedMCP server behind OAuth 2.1 bearer auth (MCP authorization spec)
Same server as /mcp. Without a valid RS256 token from the built-in IdP: 401 with `WWW-Authenticate: Bearer resource_metadata=...`. The token audience must name this resource (RFC 8707 `resource`). Destructive tools (cancel_order) need the mcp:tools:write scope, else 403 insufficient_scope.
POSTGETDELETE
/mcp/apikeyMCP server requiring an X-API-Key header
Any non-empty X-API-Key is accepted unless RUSTYBIN_MCP_API_KEY is set. Useful to demo gateway credential injection.
POSTGETDELETE
/mcp/servers/{name}Named MCP servers with tool subsets: weather, crm, devtools
Separate MCP servers for aggregation and routing demos: weather (get_weather, get_time), crm (lookup_customer, search_orders, cancel_order), devtools (echo, calculate, slow_task, failures, images, elicitation, sampling, ...). Every server also has inspect_request.
GETSSE
/mcp/sseLegacy MCP HTTP+SSE transport (2024-11-05): event stream
The first event is `endpoint` with the POST URL (/mcp/messages?sessionId=...); responses and notifications arrive as `message` events.
POST
/mcp/messagesLegacy MCP HTTP+SSE transport (2024-11-05): message endpoint
POST JSON-RPC with ?sessionId= from the endpoint event; answers 202 and replies on the stream.
GET
/.well-known/oauth-protected-resourceOAuth Protected Resource Metadata (RFC 9728) for /mcp/protected
GET
/.well-known/oauth-protected-resource/mock/{name}/_rustybin/mcpOAuth Protected Resource Metadata (RFC 9728) of a mock whose MCP server asks for a token (mcp.oauth)
GET
/.well-known/oauth-protected-resource/mcp/protectedOAuth Protected Resource Metadata (RFC 9728), path-suffixed form

A2A Agents

GET
/.well-known/agent-card.jsonA2A Agent Card (v1.0, readable by v0.3 clients) of the default echo agent, listing every demo agent
Card URLs are derived from the listener (http or https) and the Host header (Forwarded / X-Forwarded-* only with RUSTYBIN_TRUST_FORWARD). Cache-Control and ETag are set.
GET
/.well-known/agent.jsonLegacy A2A v0.3 Agent Card (url + preferredTransport) of the default agent
GETPOST
/a2aGET: directory of demo agents; POST: JSON-RPC endpoint of the default echo agent
GETPOST
/a2a/{agent}A2A JSON-RPC endpoint per agent (v1.0 methods with A2A-Version: 1.0, v0.3 methods without); GET returns the agent card
Agents: echo, weather, travel-planner, approval, flaky, secure, reject. v1.0: SendMessage, SendStreamingMessage, GetTask, ListTasks, CancelTask, SubscribeToTask, Create/Get/List/DeleteTaskPushNotificationConfig, GetExtendedAgentCard. v0.3: message/send, message/stream, tasks/get, tasks/cancel, tasks/resubscribe, tasks/pushNotificationConfig/set|get|list|delete, agent/getAuthenticatedExtendedCard.
GET
/a2a/{agent}/.well-known/agent-card.jsonAgent Card of one agent (v1.0 + v0.3 fields)
GET
/a2a/{agent}/.well-known/agent.jsonLegacy v0.3 Agent Card of one agent
ANY
/a2a/{agent}/v1/{*rest}A2A HTTP+JSON (REST) binding: message:send, message:stream, tasks, tasks/{id}, tasks/{id}:cancel, tasks/{id}:subscribe, push configs, extendedAgentCard
v1.0 with A2A-Version: 1.0 (ProtoJSON, google.rpc.Status errors), v0.3 otherwise. Paths mirror the proto: POST /message:send, POST /message:stream, GET /tasks, GET /tasks/{id}, POST /tasks/{id}:cancel, GET|POST /tasks/{id}:subscribe, POST|GET /tasks/{id}/pushNotificationConfigs, GET|DELETE /tasks/{id}/pushNotificationConfigs/{configId}, GET /extendedAgentCard, GET /card (v0.3).
GETPOSTDELETE
/a2a/webhook-sink/{id}Built-in push notification sink: POST records a notification, GET lists them, DELETE clears
Use http(s)://<this host>/a2a/webhook-sink/{id} (or just /a2a/webhook-sink/{id}) as the push URL: delivered in-process, no SSRF risk. Other targets need RUSTYBIN_A2A_PUSH_ALLOWLIST.

GraphQL

GETPOST
/graphqlGraphQL over HTTP (POST or GET ?query=; GraphiQL for browsers)
Honours operationName, application/graphql-response+json or */* (spec status codes), automatic persisted queries (extensions.persistedQuery), depth limit 10, complexity limit 500 and 30 aliases. GET cannot run mutations (405).
GET
/graphql/schemaSchema in SDL
GETWS
/graphql/wsGraphQL subscriptions over WebSocket (graphql-transport-ws and graphql-ws)
Subscriptions: ticker(count, intervalMs), orderUpdates(orderId, intervalMs).

Orchestration

POST
/orchestration/step/1Step 1: authenticate (non-empty X-Api-Key required)
POST
/orchestration/step/2Step 2: enrich with a deterministic risk score (X-Correlation-Id required)
POST
/orchestration/step/3Step 3: validate (declined when risk score >= 70 or amount > 50000)
POST
/orchestration/step/4Step 4: process (requires X-Validation-Result: approved)
GET
/orchestration/statusPipeline documentation
POST
/jsonrpcGeneric JSON-RPC 2.0 endpoint (batches, notifications, standard errors)
Methods: echo (returns params), add ([numbers] or {a, b}), subtract ([minuend, subtrahend] or named), sleep ([ms] or {ms}, capped by the instance delay limit), error ([code, message?, data?] or {code, message, data}: replies with that error). Batches (max 100; public 20) run concurrently; notifications get no reply (204 when nothing is left to answer). Errors: -32700 parse, -32600 invalid request, -32601 method not found, -32602 invalid params. HTTP status is 200 for every reply.

SOAP / XML

GETPOST
/soapSOAP 1.1 / 1.2 service (GetUser, CreateOrder, GetStatus); GET /soap?wsdl returns the WSDL
Version from the envelope namespace (or Content-Type text/xml vs application/soap+xml), dispatch by SOAPAction / action parameter, falling back to the Body element. Faults: 1.1 HTTP 500; 1.2 Code/Reason, HTTP 400 (Sender) or 500.
GET
/soap/wsdlWSDL 1.1 document (SOAP 1.1 and 1.2 bindings)

WebSocket

GETWS
/wsWebSocket echo of every text and binary frame (subprotocol echoed)
Echoes the first offered Sec-WebSocket-Protocol. Max message 1 MiB (256 KiB public), idle timeout 5 min (1 min), max lifetime 1 h (10 min); closes with 1009 / 1001.
GETWS
/ws/timeWebSocket timestamp ticker (?interval_ms=&count=), then a normal close

Reliability Testing

ANY
/flaky/{fail_rate}Fail with 503 for fail_rate percent of requests
ANY
/flaky/pattern/{pattern}Deterministic success/failure pattern (S = success, F = failure), per session
ANY
/flaky/after/{n}Succeed n times, then fail (circuit breaker trip), per session and n
ANY
/flaky/recover/{n}Fail n times, then recover (circuit breaker half-open), per session and n
POST
/flaky/resetReset the caller's counters (?scope=all resets everyone's, admin-guarded)
Counters are keyed by session (X-Rustybin-Session header, else client IP) and route. Without scope only the caller's session is reset.
GET
/flaky/statusThe caller's flaky counters

Request Bin & Webhooks

GETPOST
/binCreate a request bin (POST) or list your bins (GET)
POST with an optional JSON body {status, headers: {name: value}, body (string or JSON), delay_ms} configures the response the bin returns; the reply has the bin id and its capture, list and stream URLs. Limits: 200 bins, 100 requests and 1 MiB per bin, 24 h TTL (public mode: 100 bins, 10 per session, 50 requests and 256 KiB per bin, 1 h TTL). Bodies are captured up to 64 KiB. GET lists the bins owned by the caller's session (X-Rustybin-Session, else client IP).
ANY
/bin/{id}Capture a request into a bin (DELETE deletes the bin)
Any method except DELETE is captured and answered with the bin's configured response (default: 200 JSON acknowledgement with the sequence number, also in X-Rustybin-Bin-Seq). DELETE removes the bin; send DELETE to a sub path to capture it instead. Unknown bins are 404.
ANY
/bin/{id}/{*path}Capture a request sent to any sub path of a bin
GET
/bin/{id}/requestsList the requests captured by a bin (newest first)
?limit= caps the page (default 50). Inspection routes are never captured.
GETSSE
/bin/{id}/requests/streamLive feed of a bin's captured requests
Server-Sent Events: one `request` event per capture, with the sequence number as the event id. Last-Event-ID replays stored requests after that number. Ends when the bin expires or is deleted.
GET
/bin/{id}/requests/{n}One captured request by sequence number
GET
/webhooksWebhook signature schemes and demo secrets
POST
/webhooks/verifyVerify a Standard Webhooks signature and explain failures
Secret via X-Webhook-Secret header or ?secret= (whsec_<base64>). Checks webhook-id, webhook-timestamp (default tolerance 300 s, ?tolerance=, ?ignore_timestamp=true) and webhook-signature (v1,<base64 HMAC-SHA256 of id.timestamp.body>). 200 when valid; 401 with error timestamp_too_old, timestamp_too_new, signature_mismatch or no_supported_signature; 400 for missing_headers, missing_secret, invalid_secret, invalid_timestamp. The reply includes the expected signature.
POST
/webhooks/verify/{scheme}Verify a webhook signature: standard, github (X-Hub-Signature-256) or stripe (Stripe-Signature)
Same secret handling and error codes as /webhooks/verify. github: sha256=<hex HMAC-SHA256(secret, body)>, no timestamp. stripe: t=<unix>,v1=<hex HMAC-SHA256(secret, t.body)> with the full secret string as key and a 300 s tolerance.
GETPOST
/webhooks/signProduce a signed example webhook (headers + body) for a secret
Query: scheme=standard|github|stripe (default standard), secret (default the `demo` secret), payload (or the POST body), id, timestamp. Returns the headers, body, signed content and a curl command, ready to replay through a gateway's webhook validation.
POST
/webhooks/receive/{secret_id}Webhook receiver with a demo secret: 204 when valid, 401 otherwise
Demo secrets: `demo` = whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw, `github-docs` = It's a Secret to Everybody. The scheme is detected from the headers (webhook-signature, x-hub-signature-256 or stripe-signature).

Mock APIs (OpenAPI)

POSTGETDELETE
/mock/{name}/_rustybin/mcpMCP server of an OpenAPI mock: one tool per operation
Every deployed mock is also an MCP server (Streamable HTTP, the same protocol versions as /mcp): one tool per operation (named after the operationId, arguments from the path, query, header and cookie parameters plus `body`), the served spec as a resource. Each call runs through the mock (rules, examples, stateful data, mocked security, captures), so the tool returns what the REST call would: structuredContent {status, body}, and a status outside 2xx is a tool error. Credentials for mocked security come from the MCP request (Authorization, cookies, the API key headers and query parameters of the spec). On RUSTYBIN_MOCKS_HOST also at <name>.<domain>/_rustybin/mcp. Off with settings mcp.enabled false; mcp.exclude hides operations.
GET
/_rustybin/mocks/{name}/coverageExample coverage of a mock: which operations lack an example for their success responses
POST
/_rustybin/mocks/ai/specAI: an OpenAPI document from a plain description, checked, nothing created
POST
/_rustybin/mocks/{name}/ai/examplesAI (or the generator): examples for the operations that lack them, stored in the draft with apply
POST
/_rustybin/mocks/{name}/ai/editAI: edit the spec with an instruction, as a diff; apply saves the draft
ANY
/mock/{name}Call an OpenAPI mock (its root path)
Mocks are created from an OpenAPI 3.0 / 3.1 spec in the console (/ui/#/mocks), with POST /_rustybin/mocks or from rustybin.yaml. Unknown mocks answer 404; switched off mocks answer 503 (configurable). Every call is captured.
ANY
/mock/{name}/{*path}Call an operation of an OpenAPI mock
The path below the mock root is matched against the spec's paths (with the servers[0] path prefix, e.g. /v1, unless strip_server_prefix is set). The response comes from rules, overrides, spec examples or data generated from the schema. `Prefer: code=404`, `Prefer: example=name` and `Prefer: dynamic=true` (or X-Rustybin-Mock-Status / X-Rustybin-Mock-Example) pick a documented response. Requests are validated (warn by default: X-Rustybin-Mock-Validation: failed) and mocked security applies when enabled. Responses carry X-Rustybin-Mock, X-Rustybin-Mock-Operation and X-Rustybin-Mock-Source.
GET
/mock/{name}/_rustybin/openapi.jsonThe deployed spec of a mock, servers rewritten to the mock URL
Ready to import into a gateway or client: servers point at the mock and, with mocked security on, OAuth2 / OpenID Connect URLs point at the built-in IdP.
GET
/mock/{name}/_rustybin/openapi.yamlThe deployed spec of a mock as YAML
GETPOST
/_rustybin/mocksList mocks (GET) or create one from a spec (POST, admin)
POST {spec (text or object), name?, enabled?, deploy? (default true), force?, strip_server_prefix?, responses?, security?, operations?, mcp?}. The name defaults to a slug of info.title. Deploying runs the checks; errors answer 422 with the report unless force is set.
POST
/_rustybin/mocks/importPreview a spec: operations, security schemes, warnings, suggested name
Body: {"spec": ...} as JSON, or the spec itself (JSON or YAML). Creates nothing.
POST
/_rustybin/mocks/applyApply a rustybin.yaml document (create, update, ?prune=true deletes the rest)
Mocks must use spec_inline (rustybin mock push inlines spec files). Changed mocks are deployed even with check errors, which are listed in the result.
POST
/_rustybin/mocks/reloadRe-read RUSTYBIN_CONFIG_FILE and RUSTYBIN_MOCKS_DIR (also on SIGHUP)
POST
/_rustybin/mocks/import/fetchFetch a spec from a URL on the server (egress purpose spec_fetch), at most the spec limit
GET
/_rustybin/mocks/exportEvery mock (except the sample) as a rustybin.yaml document
GET
/_rustybin/mocks/config.schema.jsonJSON Schema of rustybin.yaml (editor completion)
GETPOSTDELETE
/_rustybin/mocks/visitSelf-service: the caller's visit (GET), start one (POST) or end it (DELETE)
With RUSTYBIN_MOCKS_SELF_SERVICE on a public instance, visitors create mocks without the admin token. A visit owns them through its manage token (X-Rustybin-Mock-Token, returned once by POST here or by the first POST /_rustybin/mocks) and lasts a time allowance: one hour, extendable to the anonymous hours, then with an email address up to the daily hours. Off: 404 rustybin_self_service_off.
POST
/_rustybin/mocks/visit/extendSelf-service: one more period for the visit (optionally with the email step)
POST
/_rustybin/mocks/visit/tokenSelf-service: replace the visit's manage token
GETDELETE
/_rustybin/mocks/visitsSelf-service operators: every running visit (GET) or end them all (DELETE)
POST
/_rustybin/mocks/visits/pauseSelf-service operators: stop new visits and visitor mocks
POST
/_rustybin/mocks/visits/resumeSelf-service operators: allow new visits again
DELETE
/_rustybin/mocks/visits/{id}Self-service operators: end one visit and delete its mocks
GETPATCHDELETE
/_rustybin/mocks/{name}A mock's details (GET), edit its draft (PATCH) or delete it
PATCH {name?, spec?, strip_server_prefix?, responses?, security?, operations?, mcp?, deploy?, force?} replaces the given sections of the draft; deploy publishes it.
POST
/_rustybin/mocks/{name}/enableSwitch a mock on
POST
/_rustybin/mocks/{name}/disableSwitch a mock off (its URL answers 503, config and history are kept)
POST
/_rustybin/mocks/{name}/deployPublish the draft after the deploy checks (?force=true ignores errors)
POST
/_rustybin/mocks/{name}/rollbackMake a previous revision live again (body {revision} optional)
GET
/_rustybin/mocks/{name}/reportDeploy-check report of the draft: a valid request and response per operation
GET
/_rustybin/mocks/{name}/exportOne mock as a rustybin.yaml document
GET
/_rustybin/mocks/{name}/specThe spec as uploaded (the draft's, JSON or YAML as written)
GET
/_rustybin/mocks/{name}/revisions/{revision}A kept revision (the draft, the live one, the deployed history, an earlier draft): its spec and settings, and what the draft changed since
POST
/_rustybin/mocks/{name}/duplicateCopy a mock under a new name (body {name} optional; default <name>-copy)
GETDELETE
/_rustybin/mocks/{name}/stateItems stored by a mock in stateful mode (DELETE resets them)
GET
/_rustybin/mocks/{name}/collections/{format}The mock's operations as a collection: postman, hurl, curl, http, k6, bruno or insomnia (?base_url=)
GETPUT
/_rustybin/mocks/{name}/operations/{op}One operation (schemas inlined, settings, sample request) or set its settings (PUT)
PUT {respond?, rules?, mode?, delay_ms?, public?} replaces the operation's settings in the draft (null clears them).
GET
/_rustybin/mocks/{name}/operations/{op}/sampleGenerate a valid request and response (?status=, ?mode=, ?example=, ?seed=)
POST
/_rustybin/mocks/{name}/operations/{op}/validateValidate a response (or request) body against the operation's schema
Body: {kind?: response|request, status?, body}. Returns the issues with JSON pointers.
POST
/_rustybin/mocks/{name}/operations/{op}/rules/testWhich rule would answer a captured call (body {rules?, request?})
Rules default to the operation's rules in the draft, the request to its latest captured call. Returns each rule's conditions with the value seen and the winner.
GETDELETE
/_rustybin/mocks/{name}/requestsRequests a mock received, newest first (DELETE clears them)
GETSSE
/_rustybin/mocks/{name}/requests/streamLive feed of a mock's requests (SSE)
GET
/_rustybin/mocks/{name}/requests/{n}One captured request by sequence number

Health & Identity

GET
/healthHealth check (200, or 503 when toggled unhealthy)
POST
/health/healthyMark the instance healthy (admin-guarded)
POST
/health/unhealthyMark the instance unhealthy: 200 here, then /health returns 503 (admin-guarded)
For gateway active health-check and failover demos. Global to the instance; the gRPC grpc.health.v1 service reports NOT_SERVING too.
POST
/health/toggleFlip the health state, 200 with the new state (admin-guarded)
ANY
/identityInstance identity: id, hostname, uptime, /identity request count, ports and config (load-balancing demos)

Harness

GET
/_rustybin/harnessHarness node status: roles, caps, allowlist, the active run
Answers on every node (enabled: false when harness mode is off). Harness mode (RUSTYBIN_HARNESS=on) makes this Rustybin a load client against allowlisted targets, see docs/plans/harness.md.
GET
/_rustybin/harness/schemaJSON Schema of a scenario, with this node's caps as bounds
GET
/_rustybin/harness/scenariosThe built-in scenarios (smoke, ramp-to-target, soak, closed-users, step-plateaus, breakpoint, spike-and-recover, pulse, burst, daily-wave) and the saved ones
GETPUTDELETE
/_rustybin/harness/scenarios/{name}A built-in or saved scenario (?format=yaml for YAML); PUT saves one under a name, DELETE removes it
Saved scenarios are validated against this node's caps, kept by name (at most 100; RUSTYBIN_HARNESS_STATE_DIR writes them to disk) and listed next to the built-in ones, whose names are taken.
POST
/_rustybin/harness/previewThe expected rate per second per layer and in total, without sending anything
Patterns are integrated exactly, so a 50 ms burst shows in its second. One point per second (coarser past an hour, at most 3600). Works with harness mode off.
POST
/_rustybin/harness/importA recording (a HAR file, or the answer of GET /_rustybin/requests) as a scenario's source
Identical requests become one template weighted by their count, so the mix is the recording's. Hosts are dropped. A secret-looking header or query parameter is removed and named in `removed`; an entry whose body has a secret-looking field is left out. Sends nothing. Works with harness mode off.
POST
/_rustybin/harness/validateValidate a scenario (JSON or YAML) against this node's caps without sending anything
GETPOST
/_rustybin/harness/runsRuns on this node; POST a scenario (JSON or YAML) to validate, pin its targets and start it
Needs harness mode and the client role. Targets must be in RUSTYBIN_HARNESS_TARGETS; link-local, metadata and control-plane targets are always refused. One run at a time per node. Answers 202 with the run and a Location header. Needs the harness scope (or admin) when the control plane is protected.
GETPATCH
/_rustybin/harness/runs/{run}Live stats of a run; PATCH tunes it while it runs (rate_multiplier, pause, resume, hold, skip_stage, set, add_layer, remove_layer)
A change is checked like a new scenario (the caps included), applied on the next scheduler pass and recorded in the run's log and report.
GETSSE
/_rustybin/harness/runs/{run}/eventsThe run's feed: a snapshot every second, change and finding events, finished at the end
POST
/_rustybin/harness/runs/{run}/stopStop a run (it drains and reports)
GET
/_rustybin/harness/runs/{run}/reportFinal report (JSON; ?format=text or junit)
POST
/_rustybin/harness/stopKill switch: stop all load on this node now
POST
/_rustybin/harness/joinJoin a cluster: {url, join_token, advertise?} (orchestrated)
Tells this node (client or server role) to join the cluster at url (<leader>/_rustybin/harness/clusters/<id>). The node then heartbeats to the leader; assignments are checked against this node's own caps and allowlist.
POST
/_rustybin/harness/leaveLeave the cluster (this node's runs of it stop)
GETPOST
/_rustybin/harness/clustersClusters this node leads; POST {cluster_id?, min_nodes?, lease?, election_timeout?, join_token?} creates one
Needs the coordinator role. The answer carries the join token once (only its hash is kept). A coordinator that is also a client or server joins its own cluster.
GETDELETE
/_rustybin/harness/clusters/{id}A cluster: members, roles, health, clock offsets, the current run
On a member of the cluster that is not its leader: 307 to the leader, with X-Rustybin-Harness-Leader. DELETE needs no active run.
GET
/_rustybin/harness/clusters/{id}/upstreamsThe server nodes' advertised URLs, for the gateway's upstream pool
POST
/_rustybin/harness/clusters/{id}/leader/step-asideBefore maintenance: the leader steps aside and the first in the succession takes over, with the run still on
Needs three or more eligible nodes. The leader stops renewing leases, the first in line stands by the ordinary election within the election timeout, and the leader follows it. If nobody takes over within four election timeouts it keeps leading.
GET
/_rustybin/harness/clusters/{id}/leaderThe leader, its term, the succession with work scores and the history of leader changes
Answered by every member, never redirected, so a controller can always find the leader. failover is available with three or more eligible nodes (RUSTYBIN_HARNESS_CONTROL_URL).
POST
/_rustybin/harness/clusters/{id}/election/voteNode route: a candidate asks an eligible node for its vote (node credential)
POST
/_rustybin/harness/clusters/{id}/election/electedNode route: a new leader announces its term (node credential); an older leader steps down
POST
/_rustybin/harness/clusters/{id}/nodesNode route: join with the join token (bearer); returns a node credential and a lease
POST
/_rustybin/harness/clusters/{id}/nodes/{node}/heartbeatNode route: heartbeat with the node credential; a long poll carrying stats, returning assignments
GETPOST
/_rustybin/harness/clusters/{id}/runsCluster runs; POST a scenario to submit it (validated, not started)
GETPATCH
/_rustybin/harness/clusters/{id}/runs/{run}Live merged stats of a cluster run, with its quorum; PATCH tunes it on every client node
A change is checked against every client node's declared capacity for its share, recorded in the run's log, carried in the cluster snapshot (it survives a failover), and followed by the nodes on their next heartbeat.
GETSSE
/_rustybin/harness/clusters/{id}/runs/{run}/eventsThe cluster run's feed: snapshots, changes, findings, scale_request, leader_changed, finished
POST
/_rustybin/harness/clusters/{id}/runs/{run}/startStart a cluster run: the rate split by capacity and a synchronised start (409 without the quorum)
POST
/_rustybin/harness/clusters/{id}/runs/{run}/stopStop a cluster run on every client node
GET
/_rustybin/harness/clusters/{id}/runs/{run}/reportThe merged report of a cluster run (JSON; ?format=text or junit)
ANY
/harnessShaped upstream: answers with the run's server profile (latency, status mix, body)
ANY
/harness/{*path}Shaped upstream at any path: a marked request gets its run's profile, anything else the default
The server side of a run (scenario `servers`): a log-normal latency from p50 and p99, a weighted status mix, a JSON body padded to body_bytes, optionally streamed in chunks after a first-byte delay. Without a harness marker: 200, 256 bytes, no delay.
GET
/.well-known/rustybin-harnessThis node's target verification token (RUSTYBIN_HARNESS_VERIFY_TARGETS)
A gateway that routes to this node serves the token, which proves the route reaches the harness before any load is sent. 404 when harness mode is off.

Control Plane

POSTGETDELETE
/_rustybin/mcpManagement MCP server: configure this Rustybin from an AI agent
An MCP server (Streamable HTTP, 2026-07-28 and the 2025 handshake versions) whose tools manage this instance: OpenAPI mocks (create, edit, check, deploy, roll back, delete, collections, stored data, AI-built specs and examples), testing (try_request in process, issue_token from the built-in identity provider, validate_payload, wait_for_requests), traffic (mock captures, the request inspector, bins, the A2A push sink), the MCP inspector (any server, the check suite) and the mock registry, the dev portal, instance settings and the outbound policy, the whole instance as rustybin.yaml with git, AI agents, sign-in sessions, usage, the harness and self-service visits. Every control route has a tool (the parity test keeps it so). Protected like the rest of the control plane; tools/list shows only what the caller may use. Connect with: claude mcp add --transport http rustybin <base>/_rustybin/mcp --header "Authorization: Bearer <token>".
GET
/_rustybin/agentsAI agents on the management MCP server, each with the user it acts for (agents.read; a user's own without it)
Every client that called /_rustybin/mcp: its name and version (clientInfo), the credential it presents and the user it acts for (owner; the admin token and control JWTs act for nobody), first and last seen, calls today and in total, open sessions, status (active, idle, revoked) and the rights that apply to it (ceiling, tool allow and deny lists, from its own override, a rule of rustybin.yaml, the default for a role of its user or the default). Plus the policy. ?owner=<user id or sign-in name> keeps one user's agents. Everyone's need agents.read; a user without it sees their own.
GETSSE
/_rustybin/agents/eventsLive feed of agent calls (agents.read; a user's own without it)
Server-Sent Events: a `call` event per management tool call (agent, owner, tool, outcome), an `agent` event when one is revoked or restored. At most 16 listeners.
GETPATCH
/_rustybin/agents/{id}One AI agent: its user, calls per tool, last errors, sessions and rights (agents.read / agents.manage; your own without them)
PATCH {"rights": {"ceiling": "read-only" | "traffic" | "manage" | "full" | a role name, "allow": [tools], "deny": [tools]}, "note": "..."}: the agent's own rights, field by field over the matching rule; "rights": null goes back to the rules. Another user's agent needs agents.manage; your own you may limit (never widen) without it.
POST
/_rustybin/agents/{id}/revokeRevoke an AI agent: refuse its calls, end its sessions, revoke its tokens (agents.manage; your own without it)
POST
/_rustybin/agents/{id}/restoreRestore a revoked AI agent (agents.manage; one of yours you revoked without it)
DELETE
/_rustybin/agents/{id}/sessionsEnd an AI agent's MCP sessions (agents.manage; your own without it)
GET
/_rustybin/config/exportThe whole instance as rustybin.yaml (admin)
Version 2 of the configuration file: the instance (title, console user), the stored settings, secret references (never values), every mock with its spec, registry entries, portal items, saved harness scenarios, agent rules and the health toggle. ?format=json for JSON, ?format=bundle for a gzipped tar with the specs as files; ?secrets=omit leaves the references out and redacts the mocks' demo credentials.
POST
/_rustybin/config/applyApply a rustybin.yaml document (admin)
{document, prune, dry_run}: the document as YAML or JSON text or a JSON object. A section that is absent is left alone; with prune, a present section is the whole truth for its part of the instance (API mocks, published items and entries, saved scenarios, stored settings it does not name are removed). The answer carries the diff computed first, each section's result and the problems; a dry run changes nothing.
GET
/_rustybin/config/schemaJSON Schema of rustybin.yaml (version 2)
POST
/_rustybin/git/webhookA signed push from a git host: pull and apply at once
For a git host, which holds no control credential: the signature is the credential (X-Hub-Signature-256 over the body, or X-Gitlab-Token). 404 until git.webhook_secret is set. A push to another branch is ignored, and a burst costs at most two pulls.
GET
/_rustybin/gitSource control status (admin)
The configured repository (git.remote, git.branch, git.path, git.auth), whether git is installed and the working copy exists, the last commit, ahead and behind (?fetch=true asks the remote), the drift between the working copy's document and the instance (what a commit would record), the deploy key's public half and the timeline of operations.
POST
/_rustybin/git/initConnect the repository (admin)
Clone the remote's branch into <state>/repo, or initialise an empty working copy when the remote has no such branch yet; an existing working copy is pointed at the remote.
POST
/_rustybin/git/commitCommit the instance's configuration (admin)
{message, checkpoints}: export the instance as a bundle into the working copy and commit it; checkpoints names local checkpoints to write to <git.path>/checkpoints/<name>.json in the same commit (default none, never by the automatic mode).
POST
/_rustybin/git/pushPush (admin)
Push the branch; a remote with commits this instance lacks answers 409 with the conflict.
POST
/_rustybin/git/pullPull and apply (admin)
{apply, dry_run, prune}: fetch and fast-forward; with apply, apply the repository's document to the instance (dry_run answers the diff without touching anything, prune makes the document the whole truth). Never merges: a diverged branch answers 409 with the two ways out.
POST
/_rustybin/git/resolveResolve a diverged branch (admin)
{with: instance | repository, message}: instance drops the local commits and commits and pushes this instance's configuration on top of the remote; repository takes the remote's branch and applies it with prune.
GETPOST
/_rustybin/git/keyThe deploy key (admin)
GET shows the public half of the ed25519 deploy key; POST {replace} generates it (ssh-keygen) into <state>/repo.key.
GET
/_rustybin/git/historyCommit history (admin)
The last 50 commits of the working copy with their file counts.
GET
/_rustybin/mcp-serversMCP inspector: every MCP server this instance serves, and the relay's policy
POST
/_rustybin/mcp-relayMCP inspector: forward one JSON-RPC message to an MCP server, the answer as SSE
Targets on this instance (/mcp*, /_rustybin/mcp, /mock/<name>/_rustybin/mcp) are served in process; other hosts go through the egress policy (RUSTYBIN_EGRESS_ALLOW). Events: relay (upstream status and headers, the relay session), message (one per JSON-RPC message), body (a non-JSON-RPC answer), error, done.
POST
/_rustybin/mcp-relay/fetchMCP inspector: fetch one OAuth discovery or token document through the instance
DELETE
/_rustybin/mcp-relay/{session}MCP inspector: end a relay session
GETPOST
/_rustybin/mcp-checksMCP inspector: run the check suite against an MCP server (JSON or JUnit), or list saved runs
GETDELETE
/_rustybin/mcp-checks/{id}MCP inspector: one saved check run (format=junit for JUnit)
GET
/_rustybin/mcp-connectionsMCP inspector: saved connections (credentials as {set, hint})
GETPUTDELETE
/_rustybin/mcp-connections/{id}MCP inspector: one saved connection; PUT and DELETE need admin
GET
/_rustybin/registryMock MCP registry: entries with their source and validity, the scenario, the sync snippet
DELETE
/_rustybin/registry/servers/{serverName}Mock MCP registry: remove a published entry (all versions, or ?version=); admin
POST
/_rustybin/registry/validateValidate a server.json document against the vendored schema and the registry's semantic checks
GET
/_rustybin/registry/browseBrowse an MCP registry (url, search, cursor, limit, version) through the egress policy; entries with their remotes
url is a registry base (https://registry.modelcontextprotocol.io by default, its host is allowed for browsing) or /mcp-registry for this instance (in process). Pages are cached for a minute.
GET
/_rustybin/portalDev portal: settings, every publishable item and its state
Every item that can be published (mock APIs, MCP servers, the mock LLM, A2A agents, the HTTP tools, the MCP registry), with its portal metadata, why a published item is hidden (a mock not deployed) and its counters.
PUTDELETE
/_rustybin/portal/items/{id}Publish an item to the dev portal (with its metadata), or unpublish it
Ids: mock:<name>, mcp:<server>, llm, a2a:<agent>, tools, registry. A mock needs manage rights (visitors only with portal.visitors), everything else admin. Body: title, summary (Markdown), tags, contact, order, credentials (show a mock's demo credentials), access (open, or closed: everyone reads it, only portal readers use it). A mock closes only when every call needs an API key or Basic user of its own (409 security_off, default_credentials, obtainable_credentials or open_operations otherwise: JWTs of the built-in identity provider and the demo client certificate are anyone's); secure: true turns its security on with random credentials in place of the defaults (api_key mode unless the spec's or basic mode closes it) and deploys it first (needs mocks.write). Closing any other item hides its snippets only: the answer's note says its endpoint stays callable.
GET
/_rustybin/portal/insightsDev portal insights: views per day, downloads by format, Try it calls
GET
/.well-known/oauth-protected-resource/_rustybin/mcpManagement MCP: protected resource metadata (RFC 9728)
Names the authorization server agents sign in with to use /_rustybin/mcp: the built-in one at /_rustybin/oauth, or RUSTYBIN_CONTROL_OAUTH_ISSUER. Only where the control plane asks for credentials (token or jwt control auth, not shared mode).
GET
/.well-known/oauth-authorization-server/_rustybin/oauthManagement MCP sign-in: authorization server metadata (RFC 8414)
GET
/.well-known/openid-configuration/_rustybin/oauthManagement MCP sign-in: the same metadata at the OpenID Connect discovery path
POST
/_rustybin/oauth/registerManagement MCP sign-in: dynamic client registration (RFC 7591, public clients)
GET
/_rustybin/oauth/authorizeManagement MCP sign-in: authorization endpoint (redirects to the console's consent page)
GETPOST
/_rustybin/oauth/requests/{id}Management MCP sign-in: a pending request, approved or denied by the console
POST
/_rustybin/oauth/tokenManagement MCP sign-in: token endpoint (authorization code with PKCE, refresh token)
POST
/_rustybin/loginConsole sign-in: a user name and password for a control token (password mode)
Never behind control-plane auth. Answers a 12 hour control token for the user (the first admin is the console user of the sign-in settings) that every control-plane route, the management MCP server and the admin guard accept, with the user's roles resolved at every request. 5 failed attempts per client address and user name and minute (20 at one user name from every address together), then 429 with Retry-After. 404 unless RUSTYBIN_CONTROL_AUTH=password (the default).
POST
/_rustybin/logoutConsole sign-out: revoke the presented sign-in token
GET
/_rustybin/sessionWho the presented credential is: user, roles, permissions, scopes, expiry, how it was obtained
A sign-in token answers its user (user, user_id, display_name), resolved from the user store at every request; every credential answers its roles and permissions (the admin token: every permission).
GETDELETE
/_rustybin/sessionsSign-in sessions (admin): list them, or DELETE to sign out everywhere
DELETE
/_rustybin/sessions/{id}End one sign-in session (admin)
PUT
/_rustybin/passwordChange your own password (0.3's route, an alias of PUT /_rustybin/me/password); the first admin's also takes a new user name
For the caller, with the current password. The first admin's password is the secret console.password_hash (and its user name console.user, 409 when RUSTYBIN_SETTINGS_LOCK holds them); other users' is in the user store. A caller who is not a user (the admin token, a control JWT) changes the first admin's, which needs users.manage. Ends the user's other sessions and answers a fresh token.
PUT
/_rustybin/me/passwordChange your own password (the current one required)
Any signed-in user: 8 to 256 characters, not the default. Ends your other sessions and answers a fresh token. 400 not_a_user for the admin token or a control JWT.
POST
/_rustybin/joinAccept an invitation: the link's token and your new password, for a sign-in token
Never behind control-plane auth; rate limited like the sign-in (5 failures per address and minute, 20 from every address together in a lane of their own). The token works once, for 72 hours; a reset's ends the user's other sessions.
GETPOST
/_rustybin/usersUsers: list them, or POST to invite one (an invitation link, shown once)
Password sign-in only (404 otherwise). POST creates the user invited (disabled until they accept) and answers a one-time link /ui/#/join/<token>, valid 72 hours, kept only as a SHA-256; at most 100 invitations are open. A caller grants only roles whose permissions it holds.
GETPATCHDELETE
/_rustybin/users/{id}One user (by id or sign-in name): read, change roles, display name, email or disabled, delete
The guard rails: the last enabled admin is never disabled, demoted or deleted (409 last_admin); nobody changes their own roles (403 own_roles); the first admin is never deleted (409 first_admin, disable it instead). Disabling ends the user's sessions.
POST
/_rustybin/users/{id}/inviteA new invitation link for a user: a password reset once they joined
Replaces the user's open invitation. A reset leaves the old password working until the link is accepted; accepting ends the user's sessions.
GETDELETE
/_rustybin/users/{id}/sessionsA user's sign-in sessions: list them, or DELETE to sign the user out everywhere
GETPOST
/_rustybin/rolesRoles: the built-in and custom roles with the permission table, or POST a custom role
Every mode (a control JWT's rb_roles may name a custom role). A role that grants users.manage or settings.security is privileged. At most 50 custom roles; a caller saves only roles within its own permissions.
GETPUTDELETE
/_rustybin/roles/{name}One role: read it and who holds it, PUT to create or change a custom role, DELETE one nobody holds
Built-in roles never change (400 built_in); a role somebody holds is not deleted (409 role_in_use).
GETPATCH
/_rustybin/meYour own account: who you are, your roles and permissions; PATCH your display name and email
Any signed-in user (400 not_a_user for the admin token or a control JWT). The password changes with PUT /_rustybin/me/password.
GETDELETE
/_rustybin/me/sessionsYour own sign-in sessions: list them, or DELETE to sign yourself out everywhere
DELETE
/_rustybin/me/sessions/{id}End one of your own sign-in sessions
GET
/_rustybin/projectsProjects: roles on part of the instance (the ones the caller is in, or every one for a caller who reads users or every mock)
A project groups mocks, saved tests and checkpoints; a user's roles in it apply to its resources on top of their instance roles. At most 100 projects. Members are listed for a caller with users.read; everyone sees their own roles.
GETPUTDELETE
/_rustybin/projects/{name}One project: read it, create or change it (description, members), delete it (?move_to= where its resources go)
PUT creates or changes the project: description and members ({user, roles}, the whole list) given replace what it has. Nobody changes their own roles in a project, and a caller gives or takes only roles whose permissions it holds there. DELETE refuses a project that holds mocks, saved tests or checkpoints (409 not_empty) unless move_to names another project or instance; the move needs the write permission of each kind in both.
GET
/_rustybin/sso/statusSingle sign-on for the sign-in screen: on, ready, the button's name, whether the password sign-in works
Never behind control-plane auth and never a secret: enabled, ready (the issuer and the client id are set), name ("Sign in with <name>"), password_sign_in. 404 outside the password sign-in.
GET
/_rustybin/sso/startStart a single sign-on: the browser goes to the identity provider (authorization code, PKCE, state, nonce)
return_to is console (the console's #/sso/<code>) or an http:// address on a loopback host (the CLI's listener); challenge is the S256 challenge of a verifier the client keeps for POST /_rustybin/sso/token. Answers 302 to the provider's authorization endpoint. At most 10 sign-ins under way per client address, 256 in all, each for ten minutes.
GET
/_rustybin/sso/callbackThe identity provider's redirect: the code is exchanged, the ID token verified, the user signed in
Register <instance>/_rustybin/sso/callback as the client's redirect URI. Verifies the ID token (issuer, audience, expiry, nonce; RS256 or ES256), maps the groups to roles, links or creates the user and sends the browser back with a one-time code (two minutes). Throttled like the sign-in: 5 failures per client address and minute, 20 from every address together in the single sign-on lane (failures here never hold back a password sign-in).
POST
/_rustybin/sso/tokenTrade the one-time code and the PKCE verifier for a sign-in token (single sign-on)
The code works once, for two minutes, and only with the verifier whose challenge started the sign-in. Answers the sign-in token of POST /_rustybin/login (12 hours, the user's roles resolved at every request). Throttled like the sign-in.
POST
/_rustybin/sso/testTest single sign-on: fetch the provider's discovery document and keys now, check the role map
Fetches discovery and the JWKS through the outbound policy (purpose sso), fresh, and answers what they say, the redirect URI to register, and the roles the role map names that do not exist. Never the client secret (its {set, hint} only).
GET
/_rustybin/auditThe audit log: who changed what, every sign-in and every refusal (audit.read)
Newest first: each entry has the time, the actor (a user's id and name, or the credential's kind: admin_token, token, key, visit, open, anonymous, system; plus the agent for a management tool call), via (api, mcp, system), the action (the route template and method, or the tool), the event a handler named (login, invite, sso, ...), the targets (kind and name: a mock, a user, a setting key), the result (status, outcome ok, error, refused or asked, the reason and permission of a refusal), the request id and the client address. Never a value, a body, a header, a password or a token. Filters: actor, action, target (kind:name for one kind), since and until (RFC 3339, a day, or an age such as 15m, 2h, 7d), result (ok, error, refused, asked, or a status code); cursor (next_cursor) and limit (1 to 500, default 100). The last 10,000 entries are kept in memory; with a state directory every entry is also in <state>/audit/ as JSON Lines, one file per UTC day (log says where and how much).
GETSSE
/_rustybin/audit/streamLive tail of the audit log (audit.read)
Server-Sent Events: an `entry` event (id: the entry's id) per new entry that matches the same filters as GET /_rustybin/audit. At most 16 listeners.
GET
/_rustybin/assistAI assist: the provider, models, whether a key is set, the tasks and usage
POST
/_rustybin/assist/testAI assist: one short call to the configured provider (admin)
POST
/_rustybin/assist/runAI assist: run a task (free_answer, explain, suggest) as SSE or one JSON answer
{task, inputs, model?, stream?}. Streams events sent, delta, json, action, usage, error, done; with stream false answers one JSON document. 503 until a provider and key are set, 429 over the caller's calls per minute, 422 when the inputs hold a stored secret.
GET
/_rustybin/assist/usageAI assist: calls, tokens, estimated cost per provider, model and task (admin)
GETPOST
/_rustybin/assist/modelsAI assist: the provider's models with tier, context, price and the jobs each runs; POST loads the list from the provider with the stored key (admin)
GETDELETE
/_rustybin/requestsList captured requests (newest first); DELETE clears them
Filters: ?session=, ?path_prefix=, ?limit=. Public mode requires ?session= and only returns that session's requests. Shared mode: a caller authenticated with an API key (X-Rustybin-Key or ?rb_key=) only sees that key's requests. DELETE with ?session= clears one session; without it, clearing everything requires the admin token.
GETSSE
/_rustybin/requests/streamLive feed of captured requests
Server-Sent Events, one `request` event per captured request. Same filters as the list.
GET
/_rustybin/requests/{id}One captured request by id
GET
/_rustybin/configEffective configuration (no secrets)
GET
/_rustybin/versionService name, version, commit and control-plane auth mode
GET
/_rustybin/readyReadiness probe: 200 while serving, independent of the /health toggle
Never authenticated (RUSTYBIN_CONTROL_AUTH), never limited by the plan, never captured by the inspector. Use it for platform readiness checks; /health is a demo toggle that may return 503. With API keys (RUSTYBIN_SHARED_MODE, RUSTYBIN_REQUIRE_KEY) it returns 503 until the key registry was loaded once.
GETPATCH
/_rustybin/settingsInstance settings: every registered key with its value, source and lock state; PATCH changes values and secrets
Admin. A stored value wins over its environment variable, which wins over the default (`source` says which). Secrets are returned as `{set, hint}`, never as values. With RUSTYBIN_SETTINGS_LOCK the lockable keys are read-only (writes answer 409). With RUSTYBIN_STATE_DIR changes persist (`persisted`), else they last until restart.
DELETE
/_rustybin/settings/{key}Forget a stored setting: back to its environment variable or default
GET
/_rustybin/diagnosticsInstance diagnostics: storage, the secrets key, load errors, listeners, outbound refusals, source control, the sign-in (admin)
Each check has an id, a status (ok, warn, fail), a message and a fix; the answer's status is the worst. Not readiness (/_rustybin/ready) and not the demo toggle (/health).
GET
/_rustybin/scenariosGateway scenarios (retries, rate limiting, credential injection, timeouts, caching, circuit breakers, request size limits, CORS, header transformation) and the caller's runs
POST
/_rustybin/scenarios/{id}/runsStart a scenario run: prepare its upstream and get the gateway setup (admin)
GETDELETE
/_rustybin/scenarios/{id}/runs/{run}A scenario run with its setup and last verdict; DELETE ends it and removes what it created (admin)
POST
/_rustybin/scenarios/{id}/runs/{run}/checkSend the run's test traffic through the gateway and compare what the client got with what reached the upstream (admin; egress purpose scenario)
GETPOST
/_rustybin/testsSaved tests: list them, or save one from a captured request or a written one (admin)
GETPUTDELETE
/_rustybin/tests/{name}A saved test: read, replace or delete it (admin)
POST
/_rustybin/tests/runRun saved tests in process or through their gateway; JSON or JUnit (?format=junit) (admin)
GETDELETE
/_rustybin/tests/runsThe history of saved-test runs, newest first (limit, cursor); DELETE forgets every run (admin)
GET
/_rustybin/tests/runs/{id}One kept test run: each test's result, the change since the previous run; JSON or JUnit (?format=junit) (admin)
GET
/_rustybin/tests/exportThe saved tests as a Hurl file, assertions included, for CI
GETPOST
/_rustybin/tests/schedulesSaved tests on a schedule: list them with their status, or create one (admin)
GETPUTDELETE
/_rustybin/tests/schedules/{name}A test schedule: read it with its status, save or replace it, delete it (admin)
POST
/_rustybin/tests/schedules/{name}/runRun a test schedule now: its tests, kept in the history, its notice when it fails (admin)
GETPOST
/_rustybin/checkpointsCheckpoints of the instance's API mocks and health: list them (?from=git: the configuration repository's, admin), or save one (admin)
GETDELETE
/_rustybin/checkpoints/{name}A checkpoint: what it holds; DELETE removes it (admin)
POST
/_rustybin/checkpoints/{name}/restoreRestore a checkpoint: mocks, their data and health back, flaky counters reset; ?dry_run=true says what would change, ?from=git restores the configuration repository's (admin)
GET
/_rustybin/egress/checkWhat the outbound policy would say to a URL, without calling it
`url` and `purpose` (a feature's name, `mcp_relay` by default). Answers `allowed`, the `reasons` for a refusal and the feature's rule. The addresses a name resolves to are checked again when a call is made.
GET
/_rustybin/egressThe outbound connection policy: the allowlist, each feature's rule and the last 100 decisions
Admin. `RUSTYBIN_EGRESS_ALLOW` (the `egress.allow` setting) applies to every outbound feature; each feature adds its own rule when configured. Link-local, metadata, multicast and unspecified addresses, this instance's control plane and (unless allowed) loopback are always refused. Decisions carry the origin, the pinned addresses and the reason.
GET
/_rustybin/metricsPrometheus metrics (requests by route template, latency, streams, egress, protocols, LLM tokens, requests and faults, limit rejections)
Text exposition format 0.0.4. Labels are bounded: the route label is the matched route template, never the raw path. Protected like the rest of the control plane (RUSTYBIN_CONTROL_AUTH); on a public instance it needs settings.read.
GET
/_rustybin/usagePlan, limits and current usage (requests, egress, in flight, streams)
Never limited. With session scope (the free plan) it reports the caller's own counters (X-Rustybin-Session header, else client IP). Plan none reports active false. Shared mode: called with an API key (X-Rustybin-Key or ?rb_key=) it reports that key's limits and daily usage (scope key).
GET
/_rustybin/keys/usagePer-key daily totals (requests, response bytes), paginated (shared mode, admin)
?day=YYYY-MM-DD (default today, UTC), ?after=<key id>, ?limit= (1 to 1000, default 1000). Rows are sorted by key id; next_after is the key id to continue from, or null. From the quota store (RUSTYBIN_QUOTA_STORE) when configured, else from this replica's counters (today and the two previous days). Needs the admin token.
GET
/_rustybin/catalogRoute catalogue as JSON (paths, methods, categories, examples)
GET
/_rustybin/statusUptime, health state and inspector counters

Docs & Exports

GET
/This landing page (always 200, safe for liveness checks)
GET
/portalDev portal (redirects to /portal/)
The read-only portal testers use: published mock APIs with Try it and downloads, MCP servers, the mock LLM, agents and tools. On once an item is published (RUSTYBIN_PORTAL), behind RUSTYBIN_PORTAL_AUTH.
GET
/portal/Dev portal entry page
GET
/portal/{*path}Dev portal assets; extension-less paths fall back to index.html
GET
/portal/api/catalogDev portal catalogue: the published items with what their pages show
Only published items; ETag and If-None-Match.
POST
/portal/api/eventsCount a portal view or Try it call (no identifiers stored)
GET
/portal/api/items/{id}/collections/{format}A published mock as a collection (Postman, Hurl, curl, .http, k6, Bruno, Insomnia)
GET
/portal/reference/{id}A published mock's spec in the API reference viewer
POST
/portal/api/loginDev portal sign-in: a user name and password for the portal's session cookie
The console sign-in's checks, throttle and answers; sets rustybin_portal (HttpOnly, SameSite=Strict, Secure behind HTTPS, Path=/portal, 12 hours), accepted under /portal only. A user holding portal.read then uses closed items. 404 without the password sign-in or with the portal off.
POST
/portal/api/logoutDev portal sign-out: end the session and clear its cookie
GET
/portal/api/sessionWho is signed in to the portal, and whether they read closed items
GET
/uiWeb console (redirects to /ui/)
Console for presenters: live traffic inspector, request bins, API explorer, AI playground, MCP inspector, A2A client, chaos controls and token lab. Embedded in the binary, works offline.
GET
/ui/Web console entry page
GET
/ui/{*path}Console assets; extension-less paths fall back to index.html
Strong ETags with Cache-Control: no-cache (If-None-Match answers 304).
GET
/openapi.jsonOpenAPI 3.0.3 specification (JSON)
GET
/openapi.yamlOpenAPI 3.0.3 specification (YAML)
GET
/docsInteractive API reference (Scalar)
GET
/export/postman.jsonPostman collection (v2.1)
The base URL defaults to the origin the request used (scheme, Host, and Forwarded / X-Forwarded-* with RUSTYBIN_TRUST_FORWARD); override it with ?base_url=https://gateway.example.com/prefix (absolute http(s) URL).
GET
/export/insomnia.jsonInsomnia export (v4)
The base URL defaults to the origin the request used (scheme, Host, and Forwarded / X-Forwarded-* with RUSTYBIN_TRUST_FORWARD); override it with ?base_url=https://gateway.example.com/prefix (absolute http(s) URL).
GET
/export/curl.shcURL shell script
The base URL defaults to the origin the request used (scheme, Host, and Forwarded / X-Forwarded-* with RUSTYBIN_TRUST_FORWARD); override it with ?base_url=https://gateway.example.com/prefix (absolute http(s) URL).
GET
/export/bruno.jsonBruno collection
The base URL defaults to the origin the request used (scheme, Host, and Forwarded / X-Forwarded-* with RUSTYBIN_TRUST_FORWARD); override it with ?base_url=https://gateway.example.com/prefix (absolute http(s) URL).
GET
/export/requests.httpVS Code / JetBrains .http file
The base URL defaults to the origin the request used (scheme, Host, and Forwarded / X-Forwarded-* with RUSTYBIN_TRUST_FORWARD); override it with ?base_url=https://gateway.example.com/prefix (absolute http(s) URL).
GET
/export/requests.hurlHurl file
The base URL defaults to the origin the request used (scheme, Host, and Forwarded / X-Forwarded-* with RUSTYBIN_TRUST_FORWARD); override it with ?base_url=https://gateway.example.com/prefix (absolute http(s) URL).
GET
/export/k6.jsk6 load-test script
The base URL defaults to the origin the request used (scheme, Host, and Forwarded / X-Forwarded-* with RUSTYBIN_TRUST_FORWARD); override it with ?base_url=https://gateway.example.com/prefix (absolute http(s) URL).
GET
/export/har.jsonHAR archive
The base URL defaults to the origin the request used (scheme, Host, and Forwarded / X-Forwarded-* with RUSTYBIN_TRUST_FORWARD); override it with ?base_url=https://gateway.example.com/prefix (absolute http(s) URL).

No endpoint matches this filter.

gRPC (port 0)

Works on every route

Quick start

curl http://localhost/echo
curl -i http://localhost/status/418
curl -X POST http://localhost/oauth/token -d 'grant_type=client_credentials&client_id=rustybin&client_secret=secret'
curl -X POST http://localhost/ai/v1/chat/completions -H 'Content-Type: application/json' \
  -d '{"model":"rustybin","messages":[{"role":"user","content":"hello"}]}'