Last modified: 2026-07-20
Runnable sample configurations covering every major feature. Each example is a self-contained sb.yml you can point the binary at directly, most with curl commands in the file header or README showing how to exercise it. Two exceptions ship no sb.yml: observability-stack is a docker compose stack, and wasm is a pair of reference WASM modules built outside the proxy config.
An installed binary works; no build needed. Install with Homebrew (brew install soapbucket/tap/sbproxy) or the curl installer (curl -fsSL https://download.sbproxy.dev | sh), then run any example from the repo root:
sbproxy examples/<dir>/sb.ymlOr build from source once, from the repo root:
make build # debug build
make build-release # optimisedmake run CONFIG=<path> launches the built proxy with whichever config you specify. All examples bind to 127.0.0.1:8080 and use a fictional *.local Host header so multiple examples can coexist on the same port without DNS.
make run CONFIG=examples/basic-proxy/sb.yml # default
make run CONFIG=examples/transform-json/sb.yml LOG_LEVEL=debugEach sb.yml opens with a comment block listing the curl commands to drive it.
Most examples include a short terminal recording (a GIF embedded in the example's README) showing the documented curl commands run against the live proxy. They are produced with VHS:
make build-release
scripts/record-tapes.sh # record every cassette
scripts/record-tapes.sh ai-gateway # record a single one
python3 scripts/gen-example-tapes.py # regenerate per-example tapesTapes live in ../docs/tapes/ and GIFs in ../docs/assets/. AI examples read provider keys from the environment while recording; the keys never appear on screen.
Auto-generated index of every example. Each row links to the example's
directory (with its sb.yml and README). Regenerated by
python3 scripts/gen-examples-catalog.py on 2026-07-09.
| Example | Description |
|---|---|
| a2a-protocol | The a2a policy enforces per-route safety on agent-to-agent traffic. Detection runs once per request and matches three signals: Content-Type: application/a2a+json (Google A2A), MCP-Method: agents.invoke (Anthropic A2A), and an optional operator route glob. When a request is detected as A2A |
| access-log | Structured JSON access log on stdout, ready for Fluent Bit / Vector / any stdout-tailing log shipper. Demonstrates every commonly-used knob on the top-level access_log: block: status and method filters, sampling, request and response header capture with the PII redactor, and the always-on secret |
| active-health-checks | A round-robin load balancer with two targets: test.sbproxy.dev and test.sbproxy.dev/status/503. Each target has a health_check block, so the proxy runs a background probe loop on each: every interval_secs: 10 it GETs the probe path (/status/200). unhealthy_threshold: 3 consecutive |
| agent-budget | Demonstrates the agent_budget policy. Per-agent rate-limit primitive keyed on the resolved agent_id (from the agent-class resolver). One bucket per named agent collapses "every request from the Cursor instance" or "every request from the same Assistant" into a single budget operators can |
| agent-skills | Demonstrates the Agent Skills v0.2.0 well-known projection. SBproxy serves a discovery manifest at /.well-known/agent-skills/index.json and re-hosts the skill bodies the manifest pins. Every body is hashed (SHA-256) at config-load time and re-hashed on every serve; tampered bodies return 503 with |
| ai-agent-alignment | Demonstrates the agent_alignment input guardrail. |
| ai-attribution-tags | Tokenomics layer: tag every AI request with the operator's project / feature / team / customer / env / agent_type / risk_tier / trace_id so the spend record lands on the right dashboard row and the downstream ledger can join token spend to business outcomes. |
| ai-bedrock-direct | Direct integration with AWS Bedrock's model-agnostic Converse API. Clients send OpenAI-shaped chat completion requests; SBproxy translates them to the Converse shape on the way out and converts the response back to OpenAI shape on the way in. Because Converse is model-agnostic, the same |
| ai-budget | Two stacked budget limits with on_exceed: downgrade. The workspace-wide cap allows up to USD 500 of spend per month and downgrades to claude-haiku-4-5 when exceeded. The per-API-key cap allows up to 1,000,000 tokens per day and downgrades to anthropic/claude-3-haiku (served by OpenRouter) |
| ai-cascade-routing | The cascade strategy walks an ordered list of (provider, model) tiers from cheapest to most expensive. Each tier's response is graded against a quality_threshold, compared against the response body's top-level confidence_score field. When the score falls below the threshold, the response is |
| ai-cel-tenant-gate | A proxy-native pattern: a CEL expression runs at the network layer before any AI provider is contacted. Pure AI gateway libraries cannot reject a request based on the surrounding request context (auth claims, tenant headers, IP, geo) without taking on the proxy role themselves. Two CEL policies |
| ai-claude | Direct integration with the Anthropic Messages API. Clients send OpenAI-shaped chat completion requests; SBproxy translates them to Anthropic's /v1/messages shape on the way out and converts the response back to OpenAI shape on the way in. The translator hoists system role messages, defaults |
| ai-content-policy-fallback | When a provider refuses a request on content-policy / safety grounds with a 4xx, route the refusal to the next (more permissive) provider in order instead of returning it. |
| ai-context-compression-mesh | This example runs the ordered AI context-compression pipeline with session summaries stored on the cluster replication substrate instead of Redis. It is the Redis-free variant of ai-context-compression-redis: the same summary_buffer and window_fit levers, with compression.state.backend: mesh |
| ai-context-compression-redis | This example runs the ordered AI context-compression pipeline with a process-wide Redis state backend. A dedicated openai-summarizer provider reduces older plain-text chat history into a bounded running summary, then window_fit applies an explicit target-model-counted input budget. The route |
| ai-context-poisoning | A single Anthropic origin with the context_poisoning input guardrail enabled. The guardrail inspects the full input, including any retrieved content carried as role: tool or role: function messages, and flags untrusted content that tries to manipulate the model before a downstream tool call. |
| ai-cost-optimized | The cost_optimized strategy scores each provider as in_flight_requests * 1000 + weight and routes to the lowest score. Cheaper providers get a lower weight and win ties when load is balanced; pricier providers get a higher weight and only run when cheaper providers saturate. Three providers are |
| ai-crawl-control | The ai_crawl_control policy returns HTTP 402 Payment Required to known AI crawler User-Agents that arrive without a Crawler-Payment token. The 402 response body explains the price and the header to retry with; the response also stamps a Crawler-Payment realm=... challenge. The OSS ledger is |
| ai-crawl-tiered | Demonstrates a three-tier paywall in front of an article-publishing origin. Known AI crawlers see a free preview window for short URLs, a charged Markdown feed for /feed/articles/*, and a charged HTML article for /article. Browsers pass through unaffected. |
| ai-dynamic-keys | Inbound virtual keys as a live, governed resource instead of lines of YAML. |
| ai-dynamic-keys-cluster | Two sbproxy replicas govern one set of virtual keys fleet-wide: mint a key on one replica and it works on the other, revoke it on one and the next request on either is denied. |
| ai-gateway-quickstart | Point any OpenAI-shaped client at sbproxy and reach OpenAI, Anthropic, or Google without changing how you build requests or parse responses. Each vendor is a separate origin here, so you pick the upstream by host (openai.local, claude.local, gemini.local); sbproxy translates the request on |
| ai-gemini-direct | Direct integration with the Google Gemini API. Clients send OpenAI-shaped chat completion requests; SBproxy translates them to Gemini's :generateContent shape on the way out and converts the response back to OpenAI shape on the way in. The translator hoists system role messages to |
| ai-guardrail-mesh | Run the input guardrails as a cascade, collect the full verdict set, and fuse it under a configurable rule, instead of blocking on the first detector that flags. |
| ai-guardrails | A full guardrail stack on a single Anthropic origin. Three input guardrails inspect the prompt before any upstream call: injection uses the built-in pattern set plus a custom phrase, pii blocks emails, phone numbers, SSNs, and credit cards, and jailbreak adds DAN-style and evil mode |
| ai-hosted-prompts | This example runs one hosted provider (Anthropic) behind the gateway and uses the admin server to manage prompt versions and try the model from a browser. Clients speak the OpenAI chat-completions shape; the gateway translates to Anthropic and back. |
| ai-llm-aware-resilience | Classify each upstream failure into an LLM-specific cause and apply a retry count per error class, instead of treating every 5xx the same. |
| ai-local-serving | Most AI-gateway examples proxy to a model server you already run. This one turns the gateway into the host: a provider's serve: block names models the gateway itself resolves, fits to the local GPU, spawns, and supervises, then registers as a local provider ahead of any cloud fallback. It is the |
| ai-mixed-traffic | Pure AI gateway libraries assume the host is "the AI gateway" and that everything that lands on it should hit a model. A real proxy can do more: serve health checks, model catalog overrides, and SDK probe endpoints alongside live AI traffic without spinning up a second host or sidecar. Path routing |
| ai-model-group | A model group is LiteLLM's core load-balancing abstraction: several deployments share one public model name, and requests to that name are spread across them. SBproxy expresses it directly. List each deployment as a provider whose models list declares the same model. The requested model selects |
| ai-model-rate-limits | Different models cost and behave differently, so they each need their own rate cap. The model_rate_limits map keys by model name and applies sliding one-minute windows for both requests and tokens. The cap fires regardless of which provider serves the model, so an alias or fallback chain that |
| ai-multi-provider | A two-provider AI gateway with input guardrails and a soft budget cap. The fallback_chain strategy tries Anthropic first (priority 1) and falls back to OpenRouter (priority 2) on a non-2xx upstream or timeout. Two input guardrails fire before any provider is contacted: the injection guardrail |
| ai-openrouter | Routes OpenAI-compatible chat completion requests through OpenRouter. Clients speak the OpenAI protocol; SBproxy injects the OpenRouter API key, forwards the request to https://openrouter.ai/api/v1, and returns the response unchanged. Four model aliases are allowlisted via allowed_models, with |
| ai-outcome-aware-routing | Route by the realized cost-per-success fed back from completed requests, not list price or live latency alone. The gateway folds each call's outcome (success / refusal / cost / latency) into a per-provider rolling estimate and sends traffic to the provider that is succeeding most cheaply, demoting |
| ai-per-surface-rate-limits | Different OpenAI surfaces have different cost and capacity profiles. Chat completions are cheap and high volume; image generation is slow and expensive; audio speech bills per character; reranking bills per document. Putting one global cap on the origin does not capture this. Per-surface rate |
| ai-policy-cel | One sandboxed CEL expression that fuses guardrail verdicts, budget state, the routing candidate, and principal context into a closed set of typed actions: block, redact, route_to:<model>, compression:<selector>, set_sink_tag:<tag>, audit:<priority>, or allow. |
| ai-predictive-budget | Degrade gracefully as a budget scope approaches its cap instead of hitting a hard cliff at 100%: warn, then downgrade to a cheaper model, then block. |
| ai-race | Race strategy fans out the request to every eligible provider in parallel, returns the first 2xx response, and cancels the losers. Trade-off: race minimises p99 latency by always taking the fastest provider for any given request; the cost is N times the API spend (one paid completion per provider |
| ai-race-routing | The thinner sibling of ai-race: this one races two same-vendor deployments (think two OpenAI regions/keys behind one logical model) with no resilience block, to isolate the routing strategy itself. ai-race races three different vendors (OpenAI, Anthropic, Groq) at once and pairs the strategy |
| ai-regex-dlp | Built-in PII detection covers the obvious patterns (email, phone, SSN, credit card). Real organisations have their own confidential vocabulary: project codenames, internal doc IDs, source-control URLs, support ticket numbers. A regex guardrail extends the input pipeline with arbitrary patterns so |
| ai-resilience | Three independent resilience signals run on the AI provider pool. Any one can eject a provider from the routing list. (1) circuit_breaker is the classic Closed -> Open -> HalfOpen state machine; five consecutive 5xx or transport errors flip a provider Open for 30s and two successful HalfOpen |
| ai-routing-fallback | Providers are listed in priority order. With routing.strategy: fallback_chain, the proxy tries the highest-priority provider first and advances to the next when an attempt fails at the transport level (connection refused, DNS failure, timeout) or returns a retriable 5xx (500, 502, 503). From the |
| ai-shadow | Each request is forwarded to the primary provider as usual; a copy is also sent to the shadow provider concurrently. The shadow response is drained and never reaches the client; metadata is logged at target=sbproxy_ai_shadow so it can be filtered into a dedicated stream with provider, status |
| ai-streaming | Streaming is on by default in the AI gateway. The minimal Anthropic origin in this example handles "stream": true requests end-to-end: sbproxy opens a server-sent-events connection to the upstream, forwards each chunk as it arrives, and harvests token usage from the final chunk into the |
| ai-usage-ledger | A verifiable, tamper-evident record of every completed LLM call. Each event is hash-chained to the previous one and (optionally) Ed25519-signed, so spend is provable, not just logged. |
| ai-virtual-keys | Two virtual keys, two teams. The frontend team's key is allow-listed to claude-haiku-4-5; the data team's key also gets claude-sonnet-4-5. A key that asks for a model outside its allow-list is rejected with 403 before any upstream call. Each credential also carries a declared budget, tags |
| ai-waste-signals | Tokenomics layer: surface tokens spent with no outcome. The proxy emits two Prometheus counters per waste class so a FinOps dashboard can answer "what fraction of our AI spend was wasted, and why?" |
| audit-log | Every state-mutating admin call emits a typed AdminAuditEvent envelope on the structured-log stream. Pair this example with the access-log example to see the two streams side by side: the access log carries one row per request, the audit log carries one envelope per mutation. |
| auth-api-key | Enforces an API key check before any upstream call. The api_key authentication provider compares the value of the configured header (X-Api-Key) against the static allowlist ["dev-key-1", "dev-key-2"]. Requests with a missing or unrecognised key are rejected with 401 inside the proxy; matching |
| auth-basic | Two-user HTTP Basic auth with a custom realm ("sbproxy demo"). Useful for quick admin panels and small internal tools. Requests without credentials get a 401 carrying a WWW-Authenticate: Basic realm="sbproxy demo" challenge so browsers prompt the user. Credentials are matched against the static |
| auth-bearer | Accepts a fixed allowlist of opaque service tokens in the Authorization: Bearer <token> header. Pick this when callers are services that already manage shared tokens and you do not need the JWT validation surface. The two configured tokens (svc-token-alpha, svc-token-beta) are matched |
| auth-bearer-dpop | A stolen Bearer token is only useful if the attacker can also replay it. RFC 9449 (DPoP, "Demonstrating Proof of Possession") binds each token to a key the legitimate client signs with on every request. The proxy reads the DPoP: header, verifies the proof, and checks the proof's JWK thumbprint |
| auth-cap | Validates Crawler Authorization Protocol (CAP) tokens on every request. CAP tokens are EdDSA-signed JWTs bound to an agent identity (sub), a request audience (aud), and a route allow-list (glob); they also carry a rps rate limit and a daily bytes budget that the proxy can enforce. The |
| auth-forward | Delegates the authentication decision to an external HTTP service. For each inbound request, sbproxy issues a sub-request to the configured URL (https://test.sbproxy.dev/status/200) using the configured method (GET) and a 5,000 ms timeout. If the sub-request returns 200 (success_status), the |
| auth-jwt | Validates HS256 JWTs against a static HMAC secret (dev-secret-change-me). The JWT must carry the configured issuer (https://issuer.local) and audience (sbproxy-demo); requests with a missing, malformed, or wrong-issuer token are rejected with 401 inside the proxy. Valid tokens flow through to |
| basic-proxy | The simplest possible sbproxy configuration. A single origin keyed on myapp.example.com forwards every inbound request to https://test.sbproxy.dev, sbproxy's public HTTP echo service. The proxy listens on 127.0.0.1:8080, matches the Host header to the configured origin, and rewrites the |
| bulk-redirects | Each origin owns its own redirect list, compiled at config-load into an O(1) lookup keyed on the request path. Three sources are supported: inline rows:, a local file via path:, or an HTTPS URL via url:. This example ships two origins. marketing.local reads redirects.csv next door |
| cel-policy | Demonstrates the expression policy, which evaluates a CEL expression per request and decides whether to allow it. This config admits requests only when the X-Tenant header equals acme. CEL header keys are normalised to lowercase with hyphens converted to underscores, so the access path is |
| circuit-breaker | Demonstrates the circuit_breaker block on a load_balancer action. The breaker is a formal Closed -> Open -> HalfOpen state machine, one instance per target. After failure_threshold consecutive failures (5xx, connect error, timeout) the breaker trips Open and every subsequent request to that |
| compression | Enables response compression on api.local for brotli, gzip, and zstd. The first algorithm in algorithms that the client advertises in Accept-Encoding wins. min_size: 512 keeps the proxy from compressing tiny payloads where the framing overhead exceeds the savings. The upstream is |
| concurrent-limit | Demonstrates the concurrent_limit policy. The limiter caps the number of in-flight requests per key, distinct from the requests-per-second rate_limiting policy. Each accepted request takes a permit; the permit is released when the request finishes (success, error, or client disconnect). Once |
| connection-pool | The connection_pool block on api.local sizes the proxy's outbound HTTP client for this origin. max_connections: 32 caps concurrent in-flight upstream connections, idle_timeout_secs: 60 reaps idle keep-alive connections after a minute, and max_lifetime_secs: 300 is the hard ceiling on any |
| content-digest | Demonstrates the content_digest policy on a webhook receiver. The proxy hashes every inbound body and compares the result to the Content-Digest: header the sender supplied. Mismatch or missing header (with on_missing: require) rejects 400 before forwarding to upstream. Useful for any |
| content-shape-negotiation | Same URL, three response shapes. The proxy reads the agent's Accept header on the way in, resolves a single content shape per request, and the response pipeline rewrites Content-Type and stamps x-markdown-tokens on the way out. The four-transform default chain (boilerplate |
| correlation-id | The proxy mints a per-request correlation ID early in the request lifecycle. With the default policy, an inbound X-Request-Id is adopted as-is so upstream callers can tie their traces to ours; otherwise the proxy generates a fresh 32-hex UUID v4. The chosen value is forwarded to the upstream |
| csrf | Demonstrates the csrf policy. Safe methods (GET, HEAD, OPTIONS) are exempt and serve as the channel through which the proxy issues the csrf_token cookie. State-changing methods (POST, PUT, DELETE, PATCH) must echo the token back in the X-CSRF-Token request header; mismatches and |
| custom-log-fields | Operator-defined custom access-log fields. observability.log.custom_fields: adds keys to each access line's custom object, computed per request from either a static value with ${...} variable interpolation or a script (CEL, Lua, or JS) evaluated against the request context. Use it to pivot |
| ddos-protection | Demonstrates the ddos_protection policy. The proxy tracks a sliding 1-second window per source IP. When the rate exceeds request_rate_threshold: 10, the offending IP is blocked for block_duration: 10s and every subsequent request returns 429 with a Retry-After header until the block |
| defense-in-depth | Layered authentication, authorisation, and inspection on a single origin. The chain answers a different question at each layer: ip_filter (is this source on the allow list?), WAF (does the request body or URI look benign?), rate_limiting (is this IP within its RPS budget?), concurrent_limit |
| dlp-catalog | The dlp policy scans the request URI and headers for matches against the configured detector set, then either tags the upstream request with a dlp-detection header (action: tag, default) or rejects with 403 (action: block). Detectors come from the built-in regex catalogue (AWS keys, GitHub |
| error-pages | The origin on api.local is protected by API key authentication (X-Api-Key: secret-key). Requests that miss the key get a 401 from the proxy, which then runs through the error_pages table. Two 401 entries cover JSON and HTML representations using Accept content negotiation, and a 403 entry |
| exposed-credentials | When a request carries Authorization: Basic <base64> whose password matches the configured exposure list, the proxy stamps the upstream request with an exposed-credential-check header (action: tag, the default) or rejects the request outright (action: block). The OSS provider is static |
| fallback-origin | The primary action proxies to test.sbproxy.dev/status/503, which always returns 503. The fallback_origin block defines a backup origin served when the primary returns a status listed in on_status (502, 503, 504) or fails at the transport level (on_error: true). Clients see the fallback's |
| forward-rules | A single origin on gateway.local dispatches incoming requests to three different inline child origins based on path. Requests to /api/* proxy to dummyjson.com with the /api prefix stripped, /admin/* returns a static JSON banner, and anything else falls through to the default action that |
| forwarding-headers | The proxy injects a standard set of forwarding headers on every upstream request: X-Forwarded-Host, X-Forwarded-For, X-Real-IP, X-Forwarded-Proto, X-Forwarded-Port, Forwarded (RFC 7239), and Via. Each header has a per-action disable_*_header opt-out flag. This example disables Via |
| grpc-h2c | Proxies plaintext gRPC traffic to an upstream gRPC server. gRPC requires HTTP/2 end-to-end, so the proxy's plain HTTP listener must speak HTTP/2 cleartext (h2c). The proxy.http2_cleartext: true flag enables Pingora's h2c preface detection on the listener so that connections that begin with the |
| headers-and-cors | Combines three sibling blocks on one origin: request_modifiers injects X-Forwarded-By: sbproxy and a per-request X-Trace-Id (from the {{ request.id }} template key) onto outbound requests while stripping the Cookie header; response_modifiers stamps X-Served-By: sbproxy and overrides |
| host-override | By default the proxy sends the upstream URL's hostname in the upstream Host header (so vhost-routed services like Vercel, Cloudflare-fronted origins, S3 website endpoints, and AWS ALBs work out of the box). When the upstream expects a different Host than its DNS name (CDN-fronted services |
| hsts | The hsts block on secure.local injects a Strict-Transport-Security header on every response. max_age: 31536000 is one year, include_subdomains: true extends the policy to every subdomain, and preload: true opts the host into the browser preload list (production-grade only after |
| idempotency | The origin on api.local opts in to Idempotency-Key handling per the IETF draft draft-ietf-httpapi-idempotency-key-header for POST / PUT / PATCH requests carrying an Idempotency-Key header. The proxy caches each upstream response under (workspace, key) and short-circuits retries: a replay with |
| ip-filter | Demonstrates the ip_filter policy. Only requests from the loopback range 127.0.0.0/8 and the private LAN range 10.0.0.0/8 are accepted; everything else is rejected with 403 before the upstream test.sbproxy.dev is contacted. A blacklist entry of 10.0.0.99/32 carves a single IP back out |
| json-schema | Shows the one-line # yaml-language-server: $schema=... directive that wires schemas/sb-config.schema.json into your editor. VS Code (with the YAML extension), the IntelliJ / JetBrains family, and Helix all honour it and validate sb.yml as you type: field-name autocomplete, typed values |
| k8s-gateway | Realistic config when SBproxy runs behind a Kubernetes Ingress (or any cluster-edge load balancer) and proxies to backend Pods that scale up and down independently. trusted_proxies honours XFF only from cluster-internal source ranges and rejects spoofed XFF from anywhere else. service_discovery |
| listing-primitive | A minimal example of the repo-native Listing primitive. |
| load-balancer | The load_balancer action dispatches each request across a pool of upstream targets using the round_robin algorithm. Two targets are configured with equal weights. Both point at test.sbproxy.dev for demonstration, so per-target traffic distribution is not visible from the response body, but |
| load-balancer-deployment | A blue-green deployment split across two LB targets. The targets carry group: blue and group: green tags. With deployment_mode.mode: blue_green and active: green, every request is routed to the green group regardless of the round-robin algorithm. To make the active group visible without |
| local-models | Routes cheap traffic to a locally-hosted model (Ollama, vLLM, LM Studio, Hugging Face TGI, or llama.cpp) and the long tail or tougher prompts to a hosted provider. The proxy presents an OpenAI-compatible interface to clients; each backend listed here also speaks OpenAI-compatible |
| lora-aware-routing | Wires the lora-aware RoutingStrategy onto a three-target load balancer pool. The strategy walks each target's metadata map, looks for a loaded_adapters array, and prefers a healthy target that already has the requested adapter warm. If none does, it returns None and the configured |
| markdown-for-agents | Demonstrates the Wave 4 content negotiation surface end-to-end. A single origin serves an article. Browsers asking for HTML get the canned HTML body. AI crawlers asking for Markdown (Accept: text/markdown) get the same article projected into Markdown, plus two response headers that surface the |
| mcp-code-mode | > Snippet, depends on a live upstream. federated_servers[].origin here > is github.example.com, an RFC 2606 reserved placeholder, not a running > MCP server, so the federated catalogue this module is generated from is > empty as-shipped. Point the origin at your own MCP server to see typed |
| mcp-federation | The mcp action turns SBproxy into a Model Context Protocol gateway. It speaks JSON-RPC 2.0 on a configured origin, aggregates the tool catalogues of one or more upstream servers, and routes tools/call requests back to the upstream that owns each tool. Per-server prefix:, rbac:, and |
| mcp-oauth-discovery | > Partially runnable. federated_servers[].origin here is > github.example.com, an RFC 2606 reserved placeholder, not a running MCP > server, so tools/call against a federated tool cannot complete > end-to-end. The discovery endpoints below (/.well-known/..., the 401 + > |
| mcp-progressive-discovery | > Snippet, depends on a live upstream. federated_servers[].origin > here is github.example.com / postgres.example.com, RFC 2606 reserved > placeholders, not running MCP servers, so the catalogue behind search > and execute is empty as-shipped and tools/call execute cannot > complete |
| mcp-rbac-quotas | > Snippet, depends on a live upstream. federated_servers[].origin here > is github.example.com, an RFC 2606 reserved placeholder, not a running > MCP server, so this config cannot be run end-to-end as-shipped: tools/list > against it hits a DNS error. Point the origin at your own MCP |
| mcp-sessions | > Partially runnable. federated_servers[].origin here is > github.example.com, an RFC 2606 reserved placeholder, not a running MCP > server, so a federated tools/call cannot complete end-to-end. Session > lifecycle (initialize, the Mcp-Session-Id header, 400/404 on a > missing or |
| mcp-tool-rollout | > Snippet, depends on a live upstream. Both federated_servers[].origin > entries here are test.sbproxy.dev, the project's public HTTP echo > service (like httpbin), not an MCP server, so neither legacy-api nor > new-api publishes a real search tool and this cannot complete > |
| mcp-tool-versioning | > Snippet, depends on a live upstream. federated_servers[].origin > here is test.sbproxy.dev, the project's public HTTP echo service (like > httpbin), not an MCP server, so it publishes no search tool and > tools/list / tools/call against it cannot complete end-to-end as > shipped. |
| migrate-litellm | A before-and-after pair for docs/migration-litellm.md. config.yaml is a small LiteLLM proxy config: two models with rate caps, latency routing, caching, a budget kwarg, and a master key. sb.yml is what sbproxy config import-litellm produces from it, annotated so you can trace every field back |
| model-cluster-split | This local topology separates one authority, one gateway, and two workers. It uses encrypted gossip and mTLS typed-state transport. The gateway and authority participate in placement without creating model engines; the two workers own the replica assignments. The gateway exposes the logical model |
| model-cluster-symmetric | This local development example runs two identical gateway/worker processes. Both replicas use one canonical cluster handle for the model controller and mesh key cache. The deployment pins one variant and spreads its two replicas across the zone label. Each process accepts managed requests locally |
| model-host-managed | This example runs the built-in pinned Qwen bootstrap artifact through the canonical single-node model host. proxy.model_host owns the deployment; provider_type: managed_model exposes it as the client model qwen on both loopback hostnames. |
| model-manifest | This example uses a catalog v2 manifest as the immutable source of truth for managed model bytes. models.yaml names the logical models and exact variants; sb.yml selects the variants this node serves. |
| mtls-client-auth | Demonstrates mutual TLS at the listener. Incoming HTTPS clients must present a certificate signed by the configured CA bundle. Failed handshakes never reach request_filter; the connection is dropped at the TLS layer. After a successful handshake the proxy strips any inbound X-Client-Cert-* |
| multi-rail-accept-payment | Both rails (x402 + MPP) configured at once. The example demonstrates the Accept-Payment header negotiation: q-value preference, first-match-wins per A3.1, the multi-rail body shape, and the 406 fallback when no rail matches. |
| object-authz | Demonstrates the object_authz policy. The gateway enforces a declarative ownership rule: the path segment named by owner_param must equal the caller's owner identity, which principal.owner_from: sub resolves from the verified JWT sub claim. A request for one tenant's data signed with |
| observability-stack | A single docker compose command boots a complete metrics, logs, and traces stack pre-wired for SBproxy: Prometheus for metrics, Grafana for visualization, Tempo for traces, Loki for logs, Arize Phoenix and Langfuse for LLM-native traces, and an OpenTelemetry collector as the single OTLP ingress. |
| oidc | OpenID Connect Relying-Party login flow. Puts SSO in front of an upstream that has no auth of its own: an unauthenticated browser is redirected to the IdP, completes the auth-code + PKCE flow, and is served with a sealed session cookie. Subsequent requests authenticate from the cookie. |
| openapi-emission | The gateway publishes an OpenAPI 3.0 document describing the routes it exposes, derived from the live config. Three things land together: rich path matchers (template for /users/{id}, regex as the escape hatch, plus prefix and exact, with per-segment regex constraints supported inline as |
| openapi-validation | The openapi_validation policy loads an inline OpenAPI document at startup and validates each request body against the matching operation's requestBody schema. Requests whose path and method are not described in the spec, or whose Content-Type has no schema, are passed through unchanged. |
| outbound-peer-pricing | Demonstrates the peer_pricing_preflight policy: when an internal agent calls a cooperating peer through this origin, SBproxy reads the peer's published llms.txt, gates the call on the configured budget, and returns a structured 402 Payment Required to the agent if the price overruns the cap. |
| outlier-detection | A round-robin load balancer with two targets: test.sbproxy.dev and test.sbproxy.dev/status/503. Load balancer targets are addressed by scheme, host, and port only; the /status/503 path on the second target is not applied to proxied requests, so both lanes reach the same echo upstream and stay |
| page-shield | Client-side script monitoring via Content Security Policy report intake. The page_shield policy stamps a Content-Security-Policy-Report-Only (or enforcing) header on every response with the configured directives plus a report-uri pointing at the proxy's intake endpoint. Browsers POST |
| pii-redaction | When pii.enabled: true is set on an AI proxy origin, the gateway redacts well-known PII shapes from the parsed JSON request body before forwarding to the upstream provider. defaults: true enables the built-in rule set: email, US SSN, credit card with Luhn check, phone, IPv4, and common API key |
| problem-details | The origin on api.local is protected by API key authentication. The operator authors a custom error_pages entry for 401 and opts in to the RFC 9457 application/problem+json default renderer for everything else via problem_details:. |
| prompt-injection-sidecar | Two origins demonstrating the prompt_injection_v2 policy with the out-of-process sidecar detector. Detection runs in a separate process instead of loading the model inside the proxy: the proxy holds one gRPC client and sends each prompt to a sidecar that implements the shared InferenceService |
| prompt-injection-v2 | The successor to the v1 prompt_injection heuristic guardrail. The v2 policy splits detection from enforcement: a swappable detector returns a numeric score plus a categorical label, and the policy maps the score onto an action (tag (default), block, or log). The OSS build ships the |
| quote-token-replay-jwks | Demonstrates the quote-token JWKS endpoint, end-to-end JWS verification, and single-use replay protection. |
| rail-lightning | Lightning rail in the Accept-Payment negotiation contract alongside x402 + MPP. The example shows how an operator declares lightning in the per-tier rails: override and what the OSS multi-rail challenge body looks like for an agent that opts into Lightning settlement. |
| rail-mpp-stripe-test | Stripe MPP (Merchant Payment Protocol) paywall in front of a markdown feed origin, configured to talk to Stripe in test mode. Operators bring their own STRIPE_SECRET_KEY=sk_test_...; the example never ships a real key. |
| rail-x402-base-sepolia | x402 v2 paywall in front of an article origin, wired against a local mock x402 facilitator so the example runs end-to-end without touching a real testnet. The README covers two paths: |
| rate-limiting | A token-bucket rate limit attached to a proxy action. The rate_limiting policy caps each client IP at 5 requests per second with a burst capacity of 10. Excess requests are rejected with HTTP 429 and a Retry-After header before the upstream is contacted, so the limit also acts as a |
| ratelimit-by-claim | Per-tenant rate limiting keyed on a JWT claim. The rate_limiting policy on api.local accepts a key: CEL expression that runs against the request context. jwt.claims.tenant_id is auto-populated from Authorization: Bearer <jwt> without signature verification (JwtAuth handles that on a |
| redis-l2-secure | This example runs Redis with a TLS-only listener, required client certificates, password authentication, and logical database 7. SBproxy verifies the generated CA, presents its client identity, authenticates as the Redis default user, and uses the service for shared L2 cache state. |
| request-limit | Demonstrates the request_limit policy. Caps the request body at 1024 bytes, the header count at 20, and the URL length at 256 characters before the test.sbproxy.dev upstream is contacted. Anything past those limits is rejected at the edge so the upstream never sees an oversized payload. |
| request-mirror | Every request matched by localhost is forwarded to the primary upstream test.sbproxy.dev as normal AND a copy is fired at https://test.sbproxy.dev/echo (the mirror). The mirror response is read and discarded; the client only sees the primary's response. Mirror traffic is fire-and-forget. |
| request-modifiers | Demonstrates the full typed shape of request_modifiers. On the way to the upstream, the proxy sets X-Source: sbproxy and Content-Type: application/json, adds X-Trace-Id: trace-001, and removes X-Internal-Token. The URL path swap rewrites /old/ to /new/, the query block sets |
| request-validator | The request_validator policy on localhost validates inbound JSON request bodies against a JSON Schema before they reach the upstream. The schema is compiled at config load, so each request is a cheap dispatch. Only requests whose Content-Type matches application/json are validated; other |
| resilience-stack | Composes four signals on a single load balancer so a flaky backend gets isolated quickly and recovers automatically without operator intervention. Active health checks mark a target unhealthy after unhealthy_threshold consecutive failed background probes (catches "the pod is gone" without waiting |
| response-caching | Demonstrates the per-origin response_cache block. Successful responses are stored in the in-memory cache for 60 seconds, keyed on the request method, host, path, and query string. The second request for the same URL is served from cache without contacting test.sbproxy.dev. With `cache_control |
| response-modifiers | Demonstrates the full typed shape of response_modifiers across two origins on 127.0.0.1:8080. api.local keeps the upstream 200, sets X-Served-By: sbproxy and Cache-Control: public, max-age=60, adds X-Trace-Id: trace-002, removes Content-Length / Server / X-Powered-By, and |
| retry-on-status | Demonstrates status-code retries across a two-target load balancer. The retry block lists 503 in retry_on, so a matching upstream response is discarded before any bytes reach the client and target selection runs again. One target points at a deliberately failing local backend, the other at |
| robots-llms-txt | Demonstrates the Wave 4 text-format policy-graph projections. Cooperative crawlers fetch one of three documents to discover an origin's pricing posture without hitting the priced content paths: |
| rsl-licensing | Demonstrates the Wave 4 policy-graph projections. A single ai_crawl_control policy plus the per-origin content_signal field drives three machine-readable documents: |
| security-headers | Demonstrates the security_headers policy. Every response from the test.sbproxy.dev upstream gains the standard browser hardening set: Strict-Transport-Security, X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, and |
| self-hosting | The serve-only quickstart from docs/self-hosting.md: one local model running on this box as provider zero, with a cloud provider after it in the same fallback array for spill. Every plane the gateway already runs (keys, budgets, guardrails, the usage ledger) applies to the local model unchanged. |
| self-hosting-macos | Stand up a self-hosted model on an Apple Silicon Mac with the embedded admin API and UI switched on. One binary from Homebrew, one config file: the gateway acquires llama.cpp and the weights on the first request and serves the model on Metal. The admin surface gives you the request log, usage |
| semantic-cache-local | Serves near-duplicate AI prompts from cache, vectorizing prompts on-box via the classifier sidecar instead of a paid provider embedding API. No per-call cost, no prompt egress, low loopback latency. |
| semantic-cache-openai | Serves near-duplicate AI prompts from cache. Each prompt is embedded and, on a cache hit (cosine similarity above the threshold), the stored completion is replayed instead of calling the provider, so the slow, billable completion is skipped and the response carries x-semcache: HIT. |
| semantic-constraint | A natural-language policy enforced by an LLM-as-judge backend. The semantic_constraint policy renders a prompt template against the request envelope (request.method, request.path, request.host, request.query), sends the rendered prompt to a configured judge endpoint, and maps the returned |
| service-discovery | Demonstrates service_discovery on a proxy action. Without service discovery, Pingora resolves the upstream hostname once when a connection is established and the connection pool reuses that connection (and that IP) for the lifetime of the pool. With service_discovery: { enabled: true }, the |
| sessions | The session block on app.local configures the encrypted cookie used to carry session state across requests. Cookie name is sb_session, max age is 3600 seconds, http_only is on, same_site is Lax, and allow_non_ssl: true lets the example run on plain HTTP for local testing. The action |
| sidecar | Sidecar-tuned sbproxy config for the per-pod fanout case: small RSS, fast cold start, no required external dependencies. Run on the workload pod's loopback interface; an init container redirects the workload's outbound traffic onto this port. |
| sni-resolve-override | Two siblings of action.url change how the proxy reaches the upstream without touching the URL itself. sni_override: cdn.provider.net sends that name in the TLS ClientHello SNI extension and validates the upstream cert against it; useful when the cert hostname differs from the URL host (typical |
| sri | Demonstrates the sri policy in observation mode. The proxy walks text/html responses, inspects every <script src="https://..."> and <link rel="stylesheet" href="https://..."> pointing at an external origin, and checks for an integrity="..." attribute that uses one of the configured |
| static-and-mock | Three origins demonstrating the two upstream-free actions. hello.local uses the static action to return a fixed plain-text body. api.local uses the mock action to return a structured JSON object after a 50 ms simulated delay with a custom X-Mock header. health.local uses static again |
| storage-action | The storage action serves files from object storage backends. It is backed by the object_store crate and supports S3, GCS, Azure Blob, and the local filesystem. This example uses the local backend pointed at /tmp/sbproxy-static so it runs without any cloud setup; production configs swap |
| transform-encoding | Demonstrates the encoding transform. A static action returns a small JSON document; the transform converts the bytes to standard base64 via encoding: base64_encode. A response_modifier switches Content-Type to text/plain; charset=utf-8 to match the new payload shape. Other valid |
| transform-html | Demonstrates the html transform on a real upstream. The proxy fetches https://test.sbproxy.dev/html (a public Moby-Dick excerpt page) and rewrites the HTML in flight: it removes the upstream <h1>, injects a stylesheet <link> at the end of <head>, prepends a banner <div> at the start of |
| transform-html-to-markdown | Demonstrates the html_to_markdown transform. The proxy fetches https://test.sbproxy.dev/html (a public Moby-Dick excerpt page) and converts the HTML body into Markdown using ATX-style headings (#, ##, ...). A response_modifier rewrites the Content-Type header to `text/markdown |
| transform-javascript | Demonstrates the javascript transform via QuickJS. The entrypoint is function transform(body) where body is the raw upstream body as a string. The script parses it as JSON, mutates the document, and returns a new JSON string. A static action seeds the input so the example runs offline. The |
| transform-json | Demonstrates the json transform. The upstream is a static action that returns a canned post document, so the example runs offline. The transform reshapes the JSON before returning it to the client by renaming userId to author_id, removing the body field, and setting a new source field |
| transform-json-projection | Demonstrates the json_projection transform in whitelist mode. Only the listed fields (id, title) survive in the response; everything else is dropped. Same shape used by GraphQL field selection or sparse fieldsets. A self-contained static action seeds a four-field document so the example |
| transform-json-schema | Demonstrates the json_schema transform. The upstream JSON response body is validated against a JSON Schema compiled once at config-load time (remote $ref resolution is disabled, so the schema must be self-contained). Two origins on 127.0.0.1:8080 make the difference visible: schema-ok.local |
| transform-lua | Demonstrates the lua_json transform. The script entrypoint is function modify_json(data, ctx) where data is the decoded JSON value (a Lua table), not a string. A self-contained static action seeds the input so the example runs offline. The script uppercases title, derives a word_count |
| transform-markdown | Demonstrates the markdown transform. A static action returns a Markdown release-notes document; the transform converts it to HTML using pulldown-cmark with smart_punctuation, tables, and strikethrough enabled. A response_modifier rewrites the Content-Type to text/html; charset=utf-8 |
| transform-payload-limit | Demonstrates the payload_limit transform. The proxy fetches https://test.sbproxy.dev/bytes/4096, which returns 4096 random bytes, and clips the response body to max_size: 256. With truncate: true, oversize bodies are silently clipped to the configured ceiling; with the default `truncate |
| transform-replace-strings | Demonstrates the replace_strings transform. Two find-and-replace rules run against the upstream body: a literal substring swap that rewrites every occurrence of internal.example.com to public.example.com, and a regex pattern that redacts any 16-digit run (e.g., a card number) with |
| transform-template | Demonstrates the template transform. A static action emits a JSON document describing an order; the template transform parses that JSON as the input context and renders a minijinja template producing a human-readable plaintext receipt. A response_modifier rewrites Content-Type to |
| trusted-proxies | When SBproxy sits behind another LB or CDN (Cloudflare, AWS ALB, Fly.io edge, internal LB), the immediate TCP peer is the LB, not the real client. The real client IP lives in the inbound X-Forwarded-For chain. proxy.trusted_proxies is the allowlist of source ranges whose forwarding headers the |
| upstream-retries | When the proxy cannot establish a TCP/TLS connection to the upstream (DNS failure, refused, unreachable, TLS handshake fail), Pingora calls back into the proxy and the request is retried. With retry.max_attempts: 3 the proxy attempts the upstream up to three times. `retry_on: [connect_error |
| use-case-air-gapped | An AI gateway with no route out and nothing to say to the outside world. The only provider is a serve: block, the weights come from a file: source pinned by per-file sha256 digests in models.yaml (the manifest doubles as a supply-chain allowlist, pull: manual closes the last fetch path) |
| use-case-coding-assistant | The gateway hosts Qwen3 14B on the local GPU and serves it under the alias claude-sonnet-4-5. Because SBproxy exposes an Anthropic-format /v1/messages bridge alongside the OpenAI wire, Claude Code reaches the local model with only a base-URL change, and OpenAI-wire clients like Cline and |
| use-case-guardrails-everywhere | One endpoint, two providers: gpt-4o-mini hosted at OpenAI, llama3.1 on an unmanaged Ollama on the same box. The guardrail mesh (injection, jailbreak, and PII detectors fused under a quorum rule) runs before provider selection, so both lanes get identical screening: two agreeing detectors block the |
| use-case-local-first | One provider array with two lanes: a serve: entry that runs qwen3-14b on this box, and a hosted provider behind it that catches overflow and anything the card cannot take. Prompts flagged with x-sbproxy-disallow-prompt-training: true route only to providers marked no_prompt_training (here |
| use-case-meter-crawlers | Two gates on one origin. The bot_auth provider verifies RFC 9421 HTTP Message Signatures (Web Bot Auth) and turns away unsigned crawlers with 401. The ai_crawl_control policy then charges verified crawlers per request: no crawler-payment token gets a 402 challenge naming the price, and each |
| use-case-own-openrouter | One ai_proxy origin in front of OpenAI and Anthropic with dynamic key management, a deliberately tiny per-key daily budget, and a hash-chained usage ledger. You mint a virtual key per team through the admin API, teams call one OpenAI-compatible endpoint where the model field picks the vendor |
| use-case-production-ops | The observability surface an on-call engineer works with: Prometheus metrics on /metrics, a JSON access log with forced emission for errors and slow requests, the admin server's health probes and request log, and graceful degradation when an upstream dies. Three origins tell the story |
| use-case-serve-on-l4 | The config for the story doc docs/use-case-serve-on-l4.md: a provider with no base_url and a one-model serve: block. |
| variables-template | The variables block declares static, per-origin key-value pairs that the template engine exposes as {{ variables.<name> }}. Environment variables surface as {{ env.<NAME> }}. This example wires both into request headers (X-Api-Version, X-Region-Label, X-Region-Env, X-Beta-Api) so |
| vault-reference | This example shows the provider-specific secret reference schemes alongside the legacy ${ENV} form, and demonstrates how the same URI can resolve to different physical vaults across tenants. |
| waf | Demonstrates the waf policy with the OWASP Core Rule Set enabled. Each request is screened for common attack signatures (SQL injection, cross-site scripting, path traversal) before it reaches the test.sbproxy.dev upstream. With action_on_match: block, test_mode: false, and `fail_open |
| wasm | Reference modules for the SBproxy WASM transform. A WASM transform is a sandboxed module loaded by the wasm transform action; SBproxy invokes it once per request body, hands the body in on stdin, and reads the transformed body back from stdout. The host ABI follows WASI preview 1 so any toolchain |
| wasm-transform | Demonstrates the wasm response-body transform. The upstream response body is piped through a sandboxed wasm32-wasi module: the body goes in on stdin, whatever the module writes to stdout becomes the new response body. Any wasm32-wasi binary works without custom glue. This example uses the |
| web-bot-auth | Cryptographic agent verification under RFC 9421 HTTP Message Signatures and the IETF Web Bot Auth draft. AI agents (crawlers, indexers, research bots) sign each request with an Ed25519 key and advertise the key id in the Signature-Input header. The gateway verifies the signature against a |
| web-bot-auth-publish | Demonstrates the web_bot_auth_publish per-origin config. SBproxy serves its own JWKS-shaped signing-key directory at /.well-known/http-message-signatures-directory and a Signature Agent Card discovery doc at /.well-known/web-bot-auth/agent-card. Verifiers (Cloudflare, AWS WAF, third-party |
| webhook-signing | Every lifecycle webhook the proxy fires (on_request, on_response) carries a structured envelope and, when secret is set on the callback, an HMAC-SHA256 signature so the receiver can verify it wasn't forged. Headers on the webhook request include X-Sbproxy-Event, X-Sbproxy-Instance |
176 examples on disk.