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: orderstenant: acmeeffects: []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.