Skip to content

Routes and scopes

Every route you publish says who owns what it addresses. That is its scope. Plat5 reads the caller’s credential, and for the route’s scope it writes the owner’s id into the path your service receives.

routes.yml
services:
api:
organization:
routes:
- path: /org/projects
upstream: /organizations/{subject.organization_id}/projects
methods: [GET, POST]

A client calls GET /org/projects with an organization’s credential. Your service receives GET /organizations/01JD8X…/projects. The client never sends an organization id, so it can’t ask for another organization’s data.

Scope Who owns it Credential Your service gets
public No one None Nothing
user The person calling Their sign-in token or a personal API key {subject.user_id}
organization The organization A member’s API key or session {subject.organization_id}
member The calling member’s own membership Same as organization {subject.organization_id} and {subject.member_id}

A scope names the owner, never the caller. On an organization route your service learns which organization, not which person or key called. Who did what is in the audit log.

Use member scope for things a member owns: their own API keys, their comments, their subscriptions, or “the projects I can reach”.

  1. Signs the caller in. A JWT from your identity provider, or a Plat5 API key or session. The wrong kind of credential for the scope is a 401.
  2. Applies the rate limit for the route, counted per IP, user, organization or member to match the scope. Over the limit is a 429.
  3. Records the request in the audit log, before forwarding it. Writes are recorded by default.
  4. Checks permissions. If the route lists required_labels, the member’s role must grant one of them, or it’s a 403.
  5. Forwards the request with the owner filled into upstream, and with Authorization and X-API-Key removed. Your service gets an X-Request-ID and a traceparent instead.
  • Reads ids from the path, and scopes every lookup by them.
  • Enforces rules that hold no matter who asks, like “an archived project is read-only”.
  • Never parses Authorization, never validates a JWT, never checks membership. That already happened.

Trusting the path only works if nothing but Plat5 can reach your service. Run services on a private network the gateway can reach.

Routes live in YAML next to your service. plat5 start publishes them locally. In production you apply them to the route registry, which keeps every revision and pushes changes to the gateway without a restart.

More: routes.md for the full format, and identity-boundary.md for the model.