# Configuration examples

> 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 - Since version 0.1.0 - Flexway v1.6.3 - https://flexway.io/en/docs/operations-examples-rest/

> Operations guide: configure, deploy and observe the gateway. What the capability does, product side: [product guide](https://flexway.io/en/docs/product-overview/); calling it: [developer guide](https://flexway.io/en/docs/developer-overview/).

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](https://flexway.io/en/docs/operations-examples-gateway/#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](https://flexway.io/en/docs/operations-planned-configurations/) | **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](https://flexway.io/en/docs/operations-examples-brokers/#nats-jetstream-publish-from-http-or-an-mcp-tool) (`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

<!-- example: examples/rest-minimal/flexway.yaml -->

```yaml
# 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:8080
```

## Identity and CORS

<!-- example: examples/identity-and-cors/flexway.yaml -->

```yaml
# 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]
```

```yaml
# 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

<!-- example: examples/rate-limits/flexway.yaml -->

```yaml
# 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]
```

```yaml
# 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

<!-- example: examples/resilience/flexway.yaml -->

```yaml
# 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
```

```yaml
# 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 default
```

## SSE and WebSocket streams

<!-- example: examples/streaming/flexway.yaml -->

```yaml
# 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
```

```yaml
# 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 cut
```

## Transformations

<!-- example: examples/transformations/flexway.yaml -->

```yaml
# 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
```

```yaml
# 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 brought
```

## Several APIs, one gateway

<!-- example: examples/multi-host/flexway.yaml -->

```yaml
# 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
```
