v0.6.0Open source · Self-host anywhere · Cloud coming soon

Write the business logic. Plat5 runs the platform.

Sign-in, organizations, roles, API keys, audit logs, rate limits, admin tools and observability, in front of services you write in any language. Running in one command.

plat5 init --template go-fiber-api --auth -y && plat5 start
projects/handler.go
// GET /organizations/:organization_id/projects
func (h *Handler) List(c fiber.Ctx) error {
	orgID := c.Params("organization_id")
	list, err := h.store.ListByOrg(c.Context(), orgID)
	if err != nil {
		return errors.InternalError()
	}
	return c.JSON(fiber.Map{"projects": list})
}

What your customers get

  • Sign-in
  • Organizations
  • Roles
  • API keys
  • Rate limits
  • Audit log
  • Traces
  • Admin tools

Every B2B product builds the same platform first.

  • sign-in
  • teams & invites
  • roles
  • API keys
  • rate limits
  • audit log
  • admin panel
  • tracing

It's months of work before your first feature, and customers don't pay for any of it. It's also where the worst bugs live: miss one organization check and a customer sees another customer's data.

Plat5 is that platform, ready before you write a line.

/fast-and-yours

Fast to start. Nothing to outgrow.

Day one

One command, everything running.

  • Sign-in, organizations, invites and roles
  • API keys, sessions and rate limits
  • Audit logs and dashboards
  • A starter API in Go, Node or Bun
  • A sample web app and an admin console to fork

Year three

Still your code.

acme/
├─ billing/    Go     routes.yml
├─ projects/   Node   routes.yml
├─ reports/    Bun    routes.yml
└─ web/        React
  • Business logic in plain services, any language, any database
  • Swap identity providers without touching them
  • Add services and teams without rebuilding auth
  • Run it on your servers, or ours

The head start of a backend-as-a-service, without the rewrite.

/how-it-works

You write the service. Plat5 does the rest.

  1. 1

    Write a service.

    Plain HTTP, any language. It reads the organization from the URL, and that's the whole integration.

  2. 2

    Declare its routes.

    A few lines of YAML give the path, whether it belongs to a user or an organization, and the permission it needs.

  3. 3

    Plat5 does the rest.

    It signs the caller in, checks their role, applies rate limits, writes the audit log, fills in the organization, and traces the request into your code.

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: 01JD8X9M2P…traceparent: 00-4bf92f35…

/product

Everything a B2B product needs. On day one.

/multi-tenancy

Multi-tenant from the first commit.

Organizations, members, invites and roles are built in. Every request gets its organization from the caller's credential, so each endpoint is isolated the moment you route it. You write no membership checks and never trust an organization id sent by the client.

More →
GET/org/members
NNorthwind ▾Invite
MemberRoleStatus
ARAna Ruizowneractive
SOSam Okaforadminactive
PNPriya Nairmemberactive
⌘deploy-botservice accountmemberactive
LCLee Chenmembersuspended

/identity

Identity, with the auth you choose.

People sign in with any OIDC provider: Auth0, Okta, Keycloak, Clerk. Or start with Plat5 Auth's email sign-in and switch later. Your services never see a token, so switching doesn't touch them.

More →
gateway environment
# Start with Plat5 Auth: email sign-in, runs anywhere
AUTH_ISSUER=https://auth.acme.com
AUTH_JWKS_URI=https://auth.acme.com/.well-known/jwks.json
AUTH_USER_ID_CLAIM=properties.user_id

# Later, move to Okta. Your services don't change.
AUTH_ISSUER=https://acme.okta.com/oauth2/default
AUTH_JWKS_URI=https://acme.okta.com/oauth2/default/v1/keys
AUTH_USER_ID_CLAIM=sub
  • Plat5 Auth
  • Auth0
  • Okta
  • Keycloak
  • Clerk
  • Any OIDC provider

/api-keys

API keys and service accounts.

Personal keys, organization keys, and service accounts for your customers' integrations. Keys carry your own prefix, so you can register them with GitHub secret scanning. Remove a member and their keys stop working.

More →
POST/org/service-accounts/{service_account_id}/api-keys

deploy-bot · service account · role: member

New key, shown once

acme-k-2-hT9qWvB3nLx0Ke7JpRz4Ys1Uc8Fd5Ga2Ti6Mo9QCopy

Starts with acme-k-, so secret scanners can spot it if it leaks.

  • ci-deployacme-k-2-hT9q…created Oct 2
  • nightly-syncacme-k-2-Mm3P…created Sep 18
  • old-importeracme-k-2-Zr0c…revoked

/audit-logs

Audit logs your customers can read.

Plat5 records every change before it reaches your service: who did it, what changed, from where, and how it ended. Each organization reads its own log through the API, so the audit-log page enterprise buyers ask for is just a frontend task.

More →
GET/org/audit-events
TimeMemberRequestResult
14:02Ana RuizPATCH /org/members/{member_id}200
{ "role": { "from": "member", "to": "admin" } }
13:58deploy-botPOST /org/projects201
13:41Sam OkaforPOST /org/invites201
13:20Lee ChenDELETE /org/projects/{project_id}403
13:12Priya NairPATCH /org/projects/{project_id}200

Written by Plat5 before each request reached the service.

/admin

Admin tools for your team.

Support staff sign in with your company login and work across every customer: look up an organization, suspend a member, revoke a key, read an audit log. They use the same endpoints your customers do, through a separate staff gateway, and every staff action goes into its own log.

More →
GET/organizations/01JD8X3K7Q

Northwind

5 members · 1 service account · since Sep 2

staff view
  • Ana RuizownerSuspend
  • Lee Chenmember · suspendedReactivate
  • deploy-bot · ci-deployacme-k-2-hT9q…Revoke key

Signed in as [email protected]. Every request is in the staff audit log.

/observability

Observability from the first request.

Every request gets an ID and a trace that follows it from Plat5 into your code. Every component exports metrics and structured logs. One flag runs Grafana with logs, traces, metrics and dashboards on your laptop. In production, send OpenTelemetry anywhere.

More →
trace 4bf92f3577b34da6 · request 01JD8X9M2P
gatewayGET /org/projects38.2ms
gatewaysign in (cached)0.4ms
gatewayrate limit1.1ms
auditrecord intent3.6ms
apiGET /organizations/{organization_id}/projects28.3ms
apiSELECT projects9.1ms
Plat5Your service
  • Rate limits per customer

    Per organization, member, user or IP, set per route. Shared across every gateway replica.

  • Roles in one file

    Name your roles and the permissions they grant. Routes say which permission they need.

  • Lifecycle events

    A feed of organizations and members arriving and leaving, to set up or clean up your data.

  • One error format

    Every error has a message you can show people and a request id support can trace.

  • Routes with history

    Publish routes as YAML. Every change is a revision you can roll back. No restarts.

  • Any language

    Services are plain HTTP. Starters in Go, Node and Bun; anything that serves HTTP works.

/integration

This is the whole integration.

No auth SDK. No middleware. No token parsing. No role checks. Your service reads the organization from the path, and you can test it with curl.

projects/handler.go
// GET /organizations/:organization_id/projects
func (h *Handler) List(c fiber.Ctx) error {
	orgID := c.Params("organization_id")
	list, err := h.store.ListByOrg(c.Context(), orgID)
	if err != nil {
		return errors.InternalError()
	}
	return c.JSON(fiber.Map{"projects": list})
}
routes.yml
services:
  api:
    organization:
      routes:
        - path: /org/projects
          upstream: /organizations/{subject.organization_id}/projects
          methods:
            GET:
            POST:
              required_labels: [projects:write]

/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 in the staff log

Your service

GET /organizations/{organization_id}/projects

One handler. Written once.

/who-its-for

Built for teams shipping B2B products.

  • Starting a B2B product

    Sign-in, teams and roles on day one. Ship your first feature this week, not this quarter.

  • Moving upmarket

    Audit logs, roles, service accounts, API keys and SSO through your identity provider. The enterprise checklist, already done.

  • Running many services

    One layer for auth, tenancy, limits, audit and tracing, in front of every service in every language.

  • Building an API product

    Branded keys, service accounts and per-customer rate limits from the start.

Start with the product. Plat5 handles the platform.

about ten minutes
go install github.com/plat5dev/cli/cmd/plat5@latestmkdir my-app && cd my-appplat5 init --template go-fiber-api --auth -yplat5 start