Skip to content

Declared routes

A flow can declare the paths and methods it answers. A caller then reaches it at /t/{tenant}<template> instead of /flows/{tenant}/{name}/run, with the same key or token, the same limits and the same answer. /run and /enqueue stay as they are.

Declaring a route

---
flowmarkdown_version: "0.1"
flow: orders
tenant: acme
effects: []
routes: [GET /orders/{id}, POST /orders]
---

Each entry is a method, a space and a template. The methods are GET, POST, PUT, PATCH and DELETE, in upper case. HEAD and OPTIONS are answered for you.

A template starts with / and is relative to the tenant. A segment is either a literal made of letters, digits and - . _ ~, or one {name} that is the whole segment and matches exactly one non-empty segment. There is no wildcard, no catch-all and no typed parameter. The trailing slash counts: /orders and /orders/ are two routes. A template has at most 16 segments and 512 bytes.

The flow above is called as GET /t/acme/orders/42, where acme is the tenant and /orders/42 the path the template matches.

When two templates both match a path, the one with more literals wins: /orders/new is chosen over /orders/{id}. A route that duplicates another route’s shape for the same method, or overlaps it with neither more specific, is refused when the flow is published; see Versioning. Routes are unique per tenant, so a tenant never sees another tenant’s.

What the flow sees

Variable Value
ctx.path.<name> The matched {name} segment, a string, percent-decoded once
ctx.query.<key> The query value, a string; a key sent more than once is an array in request order
ctx.method The method of the request as sent, so a HEAD is HEAD
ctx.route The matched template, for example /orders/{id}

ctx.path.<name> for a name that no route of the flow declares, or in a flow with no routes:, is refused when the flow is compiled. The query is read as application/x-www-form-urlencoded, so + is a space, up to 64 pairs and 8 KiB. On every other door ctx.path and ctx.query are empty objects.

The values are data. Check them in the flow’s validate step before using them.

What the caller gets

A declared route answers exactly as /run does for the same input: the same body, the same status rules, response_status:, the same headers, the same deduplication and the same refusals. The request goes through the same gate first: key or token, tenant, rate, scope and the key’s list of flows.

Request Answer
No valid credential, whether or not the path matches any route 401, the same bytes for every path and tenant
The tenant is unknown or disabled, or no route matches, or the key may not call the matched flow 404, the same message for all of them
The path matches, the method does not 405 with Allow listing the methods of the routes the key may call
The path matches only routes the key may not call 404, with no Allow
HEAD on a declared GET The headers of the GET and no body
OPTIONS on a matched path 204 with Allow
GET, HEAD, DELETE or OPTIONS with a body 400; the body is not read
A segment that is not valid UTF-8, holds a NUL, or holds %2F 400
A query over 64 pairs or 8 KiB 400
A body over the flow’s max_message_bytes 413, only for a caller that passed the gate

Allow is written in the order GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS. A POST, PUT or PATCH with no body runs the flow with an empty message, as /run does.

The gate decides before the path is looked up, so an anonymous caller learns nothing from the difference between a path that matches and one that does not. The ceiling is the flow’s own and is applied after the match, before the body is read. A refusal of a caller who has a subject is recorded in the audit chain under the route name ANY /t/{tenant}/{tail}; the concrete path is never written there, because an identifier in it can be sensitive.

If a flow is redeployed without a route, the next request to that route is 404.

In the journal

Every event of a delivery that came through a declared route carries the matched template, in the form GET /orders/{id}, in the route column of the log and in the JSON Lines file. It is the template and never the path, so an identifier in the path is not logged. A delivery through /run, /enqueue or the gRPC door has no route. See Observability.

Behind a proxy

The proxy can map a host to the /t/{tenant} prefix. The platform reads the tenant only from the path. See The gateway boundary.