@postel/admin
adminRouter() — the default-deny Fetch handler behind every adapter's bindAdminRoutes, and the options it takes.
@postel/admin exports one factory. The narrative page — routes, auth model, tenant scoping, pagination convention — is Operations → Admin API; this page is the API shape.
adminRouter
function adminRouter(
host: AdminHost,
opts?: AdminRouterOptions,
): (req: Request) => Promise<Response>;
interface AdminHost {
readonly outbound: OutboundApi;
readonly health?: () => Promise<HealthStatus>;
}
interface AdminRouterOptions {
readonly authorize?: (req: Request) => boolean | AuthDecision | Promise<boolean | AuthDecision>;
readonly resolveTenant?: (req: Request) => string | undefined;
}
interface AuthDecision {
readonly ok: boolean;
readonly tenantId?: string;
}host— anything carrying theOutboundApi; aPostelInstancewith outbound configured satisfies it. Passhealthto serveGET /health.authorize— the gate. Default-deny: with noauthorize, every request is403. Returntrue/false, or anAuthDecisionwhosetenantIdscopes every route to that tenant (scoping rules).resolveTenant— derive a tenant scope from the request (header, path) whenauthorizedoesn't carry one.
The returned handler is Fetch-native (Request → Response), so it mounts anywhere: each web adapter's admin.bindAdminRoutes(...) wraps exactly this, and fetchToExpress / fetchToFastify bridge it onto Node frameworks.
Error mapping
Route handlers map PostelErrors with the standard policy plus admin-specific codes: INVALID_QUERY → 400, MESSAGE_NOT_FOUND / TENANT_NOT_FOUND → 404, MIGRATION_REQUIRED → 503, unauthorized → 403. Bodies are { error: { code, message } }.