Configuration examples
ExampleOperationsSince version 0.1.0
On this page
Operations guide: configure, deploy and observe the gateway. What the capability does, product side: product guide; calling it: developer guide.
Every built case is a file under examples/, validated in CI like every
configuration in the repository, and what its comment claims is pinned by a test: an example
cannot drift from the code. The flexway.yaml below is the file itself; the contract is shown by
excerpt. To check it yourself: flexway validate.
| Case | What it shows | Since |
|---|---|---|
REST, the minimum (examples/rest-minimal) |
One contract, one upstream: every operation is served, nothing else | 0.1.0 |
Identity and CORS (examples/identity-and-cors) |
Two OIDC providers, the tenant from a claim, a browser application; requirements stay in the contract | 0.3.0 |
Rate limits per caller (examples/rate-limits) |
Buckets per caller, classes by method, an operation's own class | 0.1.0 |
Resilience (examples/resilience) |
Layered budgets, pool, bulkhead, circuit breaker, body limit | 0.2.0 / 0.4.0 |
SSE and WebSocket streams (examples/streaming) |
Relayed unbuffered; only silence ends a stream | 0.2.0 / 0.5.0 |
Transformations (examples/transformations) |
Headers, path, parameters; a CEL condition; a four-level chain; !name removal |
0.1.0 |
Several APIs, one gateway (examples/multi-host) |
A contract per host, reloaded without a restart | 0.1.0 |
Agent tools (MCP) (examples/mcp-tools) |
Marked operations offered as tools; a token issued for the surface | 0.1.0 |
Existing MCP server, declared list (examples/mcp-federation) |
An MCP server already running, behind the gateway: a closed list, scopes per tool, an exchanged token, discovery with the gateway's own token | 0.1.0 |
Existing MCP server, everything it announces (examples/mcp-federation-all) |
"*" chosen explicitly, a renamed tool, no token toward a server that trusts its network |
0.1.0 |
Projected and federated tools together (examples/mcp-mixed) |
A contract operation and an existing MCP server on one surface, one list | 0.1.0 |
Token exchanged toward the upstream (examples/token-exchange) |
An upstream that verifies tokens receives its own, on the caller's behalf (RFC 8693) | 0.1.0 |
Bounded agent chain (examples/agent-chain) |
Depth and age bounded, tightened by a sensitive operation, revocation seen by introspection | 0.1.0 |
API keys (examples/api-keys) |
A known integration: a key bound to a tenant and to operations, only its digest in the store, never relayed | 0.1.0 |
Client certificates vouched for by the edge (examples/mtls-edge) |
Certificates declared by URI or subject, x-forwarded-client-cert believed from the edge only |
0.1.0 |
Inference alone, external callers (examples/ai-surface) |
OpenAI-compatible surface with no contract: a scoped token, policy routing, pinning, budgets | 0.1.0 |
Inference behind a mesh (examples/ai-mesh) |
Headers from a trusted ingress, beside a contract; a rate limit of its own on /v1/** |
0.1.0 |
Cost, soft cap and fallback (examples/ai-cost) |
Prices per backend, a euro budget, a soft cap that moves the call, fallback before any byte | 0.1.0 |
| mTLS toward an upstream | The gateway's identity outside a mesh | 0.3.0 |
| The gateway's TLS listener | Client certificates verified without an edge | 0.1.0 |
| Replacing a gateway in place | A trusted BFF's rate-limit key, the errors' type, the body left to the service, two audiences |
1.0.0 |
| Shared rate, plans, shedding | Counters shared across replicas, a quota per tenant, shedding priorities | 1.0.0 |
gRPC upstream in gRPC, gRPC-Web and REST (examples/grpc-upstream) |
An annotated .proto, served to all three clients; one operation per method |
1.1.0 |
Response cache (examples/response-cache) |
A public cache in the contract, a policy over routes, a per-subject cache, the memory bound | 1.2.0 |
Response body and status (examples/body-transforms) |
A closed projection, a mask, a semantic status, the bound and the sample | 1.2.0 |
External call (examples/external-call) |
A decision of the client's system before the upstream, an enrichment after; a declared failure, a consumed budget | 1.2.0 |
| Not built yet | MQTT devices, Sparkplug B, OPC UA; plans, per-user and per-use-case budgets, signed incoming webhooks, IP lists, compression, request coalescing; signed A2A cards, delegation and registry; signed licence | Its version |
A2A agent behind the gateway (examples/a2a-agent) |
A card declared as a contract, two bindings, a required exchanged token, free reads, bounded streams, allow-listed push targets | 1.2.0 |
| Target configurations for upcoming versions | Not built: one complete example per use case - MQTT devices and industry, plans, budgets, incoming webhooks, address lists, compression, coalescing | 1.7.0, 1.8.0 |
Kafka: publish, stream, deliver (examples/async-kafka) |
A publication door that is also an MCP tool, SSE fan-out and acknowledged WebSocket, signed webhook and dead letters, a channel bound to the tenant, SASL SCRAM over TLS | 1.5.0 |
RabbitMQ in AMQP 0-9-1: a channel per person, a public feed (examples/async-amqp) |
A channel bound to the verified subject, an anonymous public feed and its resumption, structured CloudEvents, a user and a virtual host | 1.5.0 |
MQTT 5: a claim, the tenant attribute, a webhook (examples/async-mqtt) |
A channel bound to a claim, a shared topic filtered on the tenant attribute, a webhook through a shared subscription |
1.5.0 |
Avro payloads under schema, schema registry (examples/async-binary) |
An Avro body validated on publication (the operation's choice), JSON toward SSE and webhooks, binary WebSocket frames, Confluent's wire format read from a registry, the tenant's subject required, records of an ordinary Kafka producer | 1.6.0 |
NATS and AMQP 1.0 (examples/async-nats-*, examples/async-amqp1-*) |
A JetStream stream fed from HTTP or an MCP tool, SSE and WebSocket bound to the tenant, a webhook from a durable consumer; AMQP 1.0 toward Azure Service Bus and RabbitMQ 4 | 1.6.0 |
Native Kafka clients: a virtual cluster per tenant (examples/kafka-proxy) |
Kafka producers, consumers and tools of any library behind the gateway: a virtual cluster in the contract, topics, groups and transactions written in the tenant's namespace, OAUTHBEARER, an API key over PLAIN, a client certificate, SNI over TLS or a port per broker in a mesh | 1.6.0 |
REST, the minimum
# The smallest useful configuration: one contract, one upstream. Every operation of the contract is
# served and relayed there; nothing else is.
version: 1
contracts:
openapi: ./openapi.yaml
upstreams:
default:
url: http://orders.internal:8080Identity and CORS
# Two identity providers at once, the tenant from a signed claim, a browser application on its own
# origin. What each operation requires is in the contract, never here.
version: 1
contracts:
openapi: ./openapi.yaml
upstreams:
default:
url: http://orders.internal:8080
auth:
tenant_header: X-Tenant # written toward the upstream; a caller's value is overwritten
issuers:
- name: staff
discovery: https://id.example.com/realms/staff/.well-known/openid-configuration
audience: api
roles_claim: realm_access.roles
- name: customers
discovery: https://login.example.net/.well-known/openid-configuration
audience: api
tenant_claim: org
policies:
definitions:
browser:
type: cors
config:
allow_origins: ["https://app.example.com"]
allow_credentials: true
max_age: 10m
chains:
global: [browser]# excerpt of examples/identity-and-cors/openapi.yaml
paths:
/catalogue:
get:
operationId: browseCatalogue
security: [] # anonymous, said explicitly
/me/orders:
get:
operationId: myOrders
security: [ { customers: [orders:read] } ] # a scope, read from the token
/admin/report:
get:
operationId: adminReport
security: [ { staff: [admin] } ] # a role (http bearer schemes list roles)Rate limits per caller
# A token bucket per caller - the token's subject, else the client's address, read from
# X-Forwarded-For only behind a declared edge.
version: 1
contracts:
openapi: ./openapi.yaml
upstreams:
default:
url: http://search.internal:8080
server:
trusted_proxies: [10.0.0.0/8] # the edge; nobody else chooses their address
policies:
definitions:
per-caller:
type: rate-limit
config:
key: caller
classes:
read: { rps: 50, burst: 100 }
write: { rps: 10, burst: 20 }
expensive: { rps: 1, burst: 5 }
chains:
global: [per-caller]# excerpt of examples/rate-limits/openapi.yaml
paths:
/search:
get:
operationId: search
x-flexway-rate-class: expensive # its own class, whatever its method
/items:
get:
operationId: listItems # GET: class "read"
post:
operationId: createItem # POST: class "write"Resilience
# Layered budgets, connection pools, a bulkhead and a circuit breaker per upstream. Retries are
# built in: once, for an idempotent method or an Idempotency-Key, within a retry budget.
version: 1
contracts:
openapi: ./openapi.yaml
upstreams:
default:
url: http://billing.internal:8080
timeout: 3s # total budget of an exchange with this upstream
pool: { max_idle: 100, max_conns: 200, idle_timeout: 90s }
max_in_flight: 500 # bulkhead: past it, 503 at once
circuit_breaker: { failure_ratio: 0.5, min_requests: 20, window: 10s, cooldown: 5s }
exports:
url: http://exports.internal:8080
max_in_flight: 20 # a slow upstream uses up its own slots, never everyone's
server:
timeouts:
connect: 2s
read: 60s # silence between two bytes - what governs a stream
total: 30s
limits:
max_body: 2MiB # a body read for validation; past it, 413
shutdown:
drain: 20s# excerpt of examples/resilience/openapi.yaml
paths:
/invoices:
get:
operationId: listInvoices
/invoices/export:
post:
operationId: exportInvoices
x-flexway-timeout: 20s # this operation's own budget - the most precise wins
x-flexway-upstream: exports # another upstream than the defaultSSE and WebSocket streams
# SSE and WebSocket relayed as they come: no buffer, no deadline on a stream - only silence ends
# it (server.timeouts.read). Send a heartbeat more often than that.
version: 1
contracts:
openapi: ./openapi.yaml
upstreams:
default:
url: http://live.internal:8080
server:
timeouts:
read: 90s# excerpt of examples/streaming/openapi.yaml
paths:
/events:
get:
operationId: streamEvents
content: { text/event-stream: { schema: { type: string } } }
/ws:
get:
operationId: liveSocket # a WebSocket upgrade, relayed like any request
x-flexway-timeout: 2s # the total budget stops at 101: an open socket is not cutTransformations
# Enriching a flow without touching the upstream: named policies, a CEL condition, composed
# global -> host -> routes -> operation, the most precise wins, `!name` removes.
version: 1
contracts:
openapi: ./openapi.yaml
upstreams:
default:
url: http://orders.internal:8080
auth:
issuers:
- name: customers
discovery: https://login.example.net/.well-known/openid-configuration
audience: api
policies:
definitions:
plan-header:
type: header-inject
when: "has(auth.subject)" # CEL: authenticated callers only
config:
set: { x-plan: "{{ auth.claims.plan }}", x-operation: "{{ route.operation_id }}" }
on_missing: skip # skip | empty | fail - required for an expression
strip-server:
type: header-remove
phase: response # on what the upstream answers
config: { names: [server, x-powered-by] }
v2-path:
type: rewrite-path # the upstream's path, never the served one
config: { to: "/v2/orders/{id}" }
channel:
type: query-inject
config: { set: { channel: gateway } }
chains:
global: [strip-server]
routes:
"/orders/*": [v2-path, channel] # * one segment, ** any number# excerpt of examples/transformations/openapi.yaml
paths:
/orders/{id}:
get:
operationId: getOrder
x-flexway-policies: [plan-header] # this operation alone gets the caller's plan
/orders/{id}/legacy:
get:
operationId: getLegacyOrder
x-flexway-policies: ["!strip-server"] # removes a policy a broader level broughtSeveral APIs, one gateway
# Two APIs on one gateway, each on its own host, neither visible from the other - and reloaded
# without a restart when a file changes.
version: 1
contracts:
openapi: ./public.yaml # every host without contracts of its own
upstreams:
catalogue: { url: "http://catalogue.internal:8080" }
backoffice: { url: "http://backoffice.internal:8080" }
hosts:
api.example.com:
default_upstream: catalogue
admin.example.com:
default_upstream: backoffice
contracts: [./backoffice.yaml] # exactly this, nothing of public.yaml
server:
reload:
interval: 5s # "0s": SIGHUP only