1. 1

    Who is calling?

    Your identity provider says, from its token. Or Plat5 does, from one of its API keys or sessions.

  2. 2

    Which organization?

    The credential decides. Never a header, a body field, or a path the client chose.

  3. 3

    May they call this route?

    The member's role grants permissions. The route says which one it needs.

Client sends

GET /org/projectsX-API-Key: acme-s-2-Q7vN…

Plat5

  • Signed in as a member of Northwind
  • Role allows GET /org/projects
  • Under the organization's rate limit
  • Recorded in Northwind's audit log
  • Filled in the organization

Your service receives

GET /organizations/01JD8X3K7Q/projectsX-API-Key removedX-Request-ID: 7d6f1c2e…traceparent: 00-4bf92f35…

/why

Why your services stay simple, and stay safe.

  • 01

    Your code never handles credentials.

    Plat5 signs callers in and removes the token or key before forwarding. Your service gets ids, a request id and a trace. There is nothing to leak and nothing to validate.

  • 02

    The credential picks the organization.

    A client can't ask for another customer's data, because it never names a customer. A route you publish tomorrow is isolated the same way, with no new checks.

  • 03

    Permissions are checked before your code runs.

    Roles come from one file, and routes say which permission they need. A missing permission is refused at the gateway, so services never see a role.

  • 04

    Every change is recorded before it is forwarded.

    If the audit log can't take the record, the request is refused and your service is never called. A change can't happen without its record.

  • 05

    Same service, two front doors.

    Customers come through the Plat5 gateway. Your staff come through Operator, on your company login. Both call the same paths, so your support tools are your product's own API.

  • 06

    Everything else is decided once.

    Errors, request ids, lists, logs and audit details each have one standard. Nobody designs them again for a new service, and every service answers the same way.

/every-caller

Write it once. Everyone can use it.

Your web app, your customers' scripts and your support team all call the same code. Permissions and the audit log apply to each of them. You never build a separate admin API or integration API.

Your web app

A member, with a session

X-API-Key: acme-s-2-…

A customer's integration

A service account, with a key

X-API-Key: acme-k-2-…

Your support team

Staff, with your company login

Authorization: Bearer …

Plat5 gateway

Fills in the caller's organization

Operator

Staff name the organization. Every request goes in the staff log

Your service

GET /organizations/{organization_id}/projects

One handler. Written once.

/request

What happens to a request.

In this order, every time.

  1. 01Sign inJWT, API key or session401
  2. 02Rate limitPer IP, user, org or member429
  3. 03RecordAudit intent, before anything changes503
  4. 04Check permissionThe member's role labels403
  5. 05Fill in the ownerInto the upstream path
  6. 06ForwardCredentials removed, trace added502
  7. 07Record outcomeAfter the response
Each step can end the request with the code below it. Your service is called at step six.

/scopes

Every route says who owns it.

The scope decides which credential a route takes and which ids your service receives. A scope names the owner, never the caller.

Routes and scopes →
ScopeOwnerCredentialYour service gets
publicNo oneNone—
userThe person callingSign-in token or personal keyuser_id
organizationThe organizationMember key or sessionorganization_id
memberThe member's own recordsMember key or sessionorganization_id, member_id

/architecture

What runs.

Four small processes and three stores, in one Docker Compose stack. Your services live on a private network that only the gateway can reach: that's what makes the path trustworthy.

Clients

Your web app
Customer integrations
Scripts and CLIs

Edge

gatewayRust, on Pingora. Sign-in, rate limits, audit, permissions, path rewrite
Your identity providerAny OIDC. Or Plat5 Auth

Private network

identityGo. People, organizations, members, keys, sessions
auditGo. Organization and personal logs
route-registryRust. Route revisions, pushed to the gateway
Your servicesAny language. Your databases

State

PostgresIdentity, audit, routes
etcdLive route map
ValkeyRate-limit counters

/ideas

The ideas behind it.

A few decisions shape everything above. The full reasoning is in the model.

  • A route names its owner, never its caller.

    The scope says who owns what the route addresses. Your service stores owners.

  • Services see owners, not actors.

    Who did something is in the audit log. When it matters to your product, it becomes a record the member owns, like a comment or an approval.

  • A credential is never narrower than its member.

    To give a machine less, give it a service account with a smaller role.

  • Plat5 decides only about what it stores.

    Organizations, members and roles. Access to individual resources, like a private project, belongs to a policy engine.

  • Your roles, not ours.

    Plat5 ships a starter roles file and gives no role special meaning.

  • Product features never read the audit log.

    It's for reporting and compliance, so it can be off, restricted, or trimmed without breaking anything.

Read the model →

See it work in ten minutes.