Adding a route handler under applications/main/src/app/api/**, and choosing the right permission gate.
applications/main/src/app/api/my-thing/route.ts
import { NextRequest, NextResponse } from "next/server";
import { PERMISSIONS } from "@/lib/permissions";
import { requirePermission } from "@/lib/require-permission";
/**
* One sentence on what this returns.
* Requires `processes:audit` — say which, and why that one.
*/
export async function GET(request: NextRequest) {
const denied = await requirePermission(request, PERMISSIONS.PROCESSES_AUDIT, {
message: "processes:audit permission required",
});
if (denied) return denied;
// …
return NextResponse.json({ ok: true });
}
requirePermission returns a response when denied and null when allowed. Return it
immediately — do not invert the check.
applications/main/src/middleware.ts matches /api/:path* and, before your handler:
/api/auth/login, /api/auth/callback, /api/auth/my-organizations throughX-Process-Platform-Organization matches org_id in the tokenSo your handler can assume an authenticated, org-scoped caller. It still must check which permission.
Two exceptions to know about:
Optional-auth routes. GET /api/process/{id} and the file-download route are
allowed through without a token, because sharing may grant
anonymous access. The handler decides. If you add a route that link-sharing should
reach, add it to isOptionalAuthProcessRead.
| Need | Use |
|---|---|
| Read one process | requireProcessRead — honors owner, read_all, and sharing |
| Write one process | requireProcessWrite |
| Read across processes, no write | PROCESSES_AUDIT |
| Read across processes, admin | PROCESSES_READ_ALL — also confers write |
| Templates | TEMPLATES_READ (templates are code-owned; there is no write permission) |
Prefer processes:audit over processes:read_all for anything read-only. That
distinction is the reason audit exists.
For per-process access always use the requireProcessRead / requireProcessWrite
helpers rather than checking a permission string — they encode the four-way resolution
(owner, admin, assignment, link visibility) that a bare permission check misses.
platform/contracts/src/permissions.ts — add to PERMISSIONS and PERMISSIONS_LIST.
Then in Auth0: add it to the API, add it to the relevant organization roles, and assign those roles at org membership level. A permission that exists in code but not in Auth0 is never granted to anyone.
If your route accepts context updates, filter to permitted keys before merging:
import { filterPermittedInputs } from "@/lib/input-permissions";
Dropping unauthorized keys silently is the established behavior — do not error, since a partial write by a partially-permitted actor is a normal case.
Always go through appendStepContextAudit, never assign to process.context directly:
appendStepContextAudit(process, principal.userId, stepKey, updates);
Use SYSTEM_STEP_CONTEXT_USER_ID when the write is automation rather than a person.
A direct assignment leaves no audit trail and makes the change invisible to
audit tooling.
applications/main/src/lib/openapi.ts is a hand-maintained spec powering the Swagger UI at /docs/api.
It already drifts from the routes.
Do not treat it as a contract. Update it if you are adding something a consumer needs to discover, but the route implementation is the source of truth. See known issues.
npx tsc --noEmit
Then confirm:
401403200