NNO IAM Service API
Identity and Access Management — authentication, roles, permissions, API keys, and service tokens.
The IAM service exposes two route families: routes documented by the OpenAPI spec below (operator endpoints, OAuth device flow, organization invitations, profile, internal hooks), and additional surfaces mounted by Better Auth and the user-facing org-setup endpoint that are documented manually in the next section.
Manually Documented Surfaces
The endpoints below are mounted on the IAM worker but are not enumerated by the @hono/zod-openapi document because they are either delegated to a third-party handler (Better Auth) or do not declare an OpenAPI route definition. Treat this section as authoritative for those surfaces.
Better Auth (/api/auth/*)
Mounted by createAuthApp() in services/iam/src/core/index.ts. The handler is the standard Better Auth Hono integration with the organization, magicLink, and apiKey plugins enabled. Common endpoints include:
| Method | Path | Description |
|---|---|---|
POST | /api/auth/sign-up/email | Email/password sign-up. |
POST | /api/auth/sign-in/email | Email/password sign-in. Returns a session token + cookie. |
POST | /api/auth/sign-in/magic-link | Issues a magic-link email via Resend. |
GET | /api/auth/magic-link/verify | Verifies a magic-link token and creates the session. |
POST | /api/auth/sign-out | Revokes the current session. |
GET | /api/auth/get-session | Returns the current Better Auth session. |
POST | /api/auth/organization/* | Better Auth organization plugin (create, list, set-active, members, invitations). |
Additional auth-adjacent routes mounted alongside Better Auth:
| Method | Path | Description |
|---|---|---|
POST | /api/validate-session | Service-to-service session validation. Used by the gateway and the registry. |
GET | /api/audit/* | Audit log read endpoints (authentication and authorization events). |
GET | /api/sessions | List the caller's active sessions. |
DELETE | /api/sessions/:id | Revoke a specific session. |
GET | /api/nno/session | Enriched NNO session — adds permissions, tenant context, and effective role on top of the Better Auth session. |
GET | /health | Health check. Returns { status: "healthy" | "degraded", checks: {...} }. |
For the canonical schema of each Better Auth endpoint, see the upstream documentation; the IAM worker does not modify request or response shapes.
POST /api/nno/org-setup
User-facing organization + platform creation, called from the console "Create Organization" page.
-
Auth: requires a Better Auth session cookie (no Bearer token path).
-
Body:
{ "name": "string (1-100)", "slug": "string (1-63, ^[a-z0-9-]+$)" } -
Behaviour: creates the IAM organization (Better Auth
organizationplugin) and the matching Registry platform atomically. If the Registry call fails, the IAM organization is rolled back. -
Response:
201with{ orgId, platformId, slug, name }on success.400for validation errors,401if no session,409if the slug is taken.
Source of truth: services/iam/src/routes/nno-org-setup.ts.
List role→permission mappings for an organization
Query Parameters
1 <= lengthResponse Body
application/json
application/json
curl -X GET "https://iam.svc.nno.app/api/nno/roles?orgId=string"nullnullUpsert a role→permission mapping for an organization
Request Body
application/json
1 <= length1 <= length0 <= itemsResponse Body
application/json
application/json
application/json
curl -X POST "https://iam.svc.nno.app/api/nno/roles" \ -H "Content-Type: application/json" \ -d '{ "orgId": "string", "role": "string", "permissions": [ "string" ] }'nullnullnullcurl -X DELETE "https://iam.svc.nno.app/api/nno/roles/string/string"nullList permission grants for an organization
Query Parameters
1 <= lengthResponse Body
application/json
application/json
curl -X GET "https://iam.svc.nno.app/api/nno/grants?orgId=string"nullnullCreate a per-user permission grant or denial
Request Body
application/json
1 <= length1 <= length1 <= length1 <= length0 < value < 100000000000Response Body
application/json
application/json
curl -X POST "https://iam.svc.nno.app/api/nno/grants" \ -H "Content-Type: application/json" \ -d '{ "userId": "string", "orgId": "string", "permission": "string", "granted": true, "grantedBy": "string" }'nullnullcurl -X DELETE "https://iam.svc.nno.app/api/nno/grants/string"nullCreate a Better Auth organization for a tenant
Request Body
application/json
1 <= length <= 100^[a-z0-9-]+$1 <= length <= 631 <= length"tenant""nno-operator" | "tenant"Response Body
application/json
application/json
application/json
application/json
curl -X POST "https://iam.svc.nno.app/api/nno/organizations" \ -H "Content-Type: application/json" \ -d '{ "name": "string", "slug": "string", "ownerId": "string" }'nullnullnullnullLook up an organization by slug
Path Parameters
Response Body
application/json
application/json
curl -X GET "https://iam.svc.nno.app/api/nno/organizations/string"nullnullBootstrap the NNO operator org
Response Body
application/json
application/json
application/json
application/json
curl -X POST "https://iam.svc.nno.app/api/nno/bootstrap"nullnullnullnullValidate a raw API key
Request Body
application/json
1 <= lengthResponse Body
application/json
application/json
application/json
curl -X POST "https://iam.svc.nno.app/api/nno/apikey/validate" \ -H "Content-Type: application/json" \ -d '{ "key": "string" }'nullnullnullIssue a short-lived service-to-service JWT
Request Body
application/json
1 <= length1 <= lengthResponse Body
application/json
application/json
application/json
application/json
curl -X POST "https://iam.svc.nno.app/api/nno/service-token" \ -H "Content-Type: application/json" \ -d '{ "serviceId": "string", "targetService": "string" }'nullnullnullnullcurl -X POST "https://iam.svc.nno.app/api/v1/cli/whoami"{
"email": "string",
"platformId": "string",
"role": "string",
"expiresAt": "string"
}{
"error": {
"code": "string",
"message": "string",
"requestId": "string"
}
}Request a device authorization code (RFC 8628 §3.2)
Request Body
application/json
"nno-cli"Response Body
application/json
curl -X POST "https://iam.svc.nno.app/oauth/device/code" \ -H "Content-Type: application/json" \ -d '{ "client_id": "nno-cli" }'{
"device_code": "string",
"user_code": "string",
"verification_uri": "string",
"expires_in": 0,
"interval": 0
}Authorize a pending device code (called by nno.app/activate)
Request Body
application/json
1 <= lengthResponse Body
application/json
application/json
application/json
application/json
curl -X POST "https://iam.svc.nno.app/oauth/device/authorize" \ -H "Content-Type: application/json" \ -d '{ "user_code": "string" }'{
"ok": true
}nullnullnullPoll for the device access token (RFC 8628 §3.5)
Request Body
application/json
"urn:ietf:params:oauth:grant-type:device_code""nno-cli"Response Body
application/json
curl -X POST "https://iam.svc.nno.app/oauth/device/token" \ -H "Content-Type: application/json" \ -d '{ "grant_type": "urn:ietf:params:oauth:grant-type:device_code", "device_code": "string", "client_id": "nno-cli" }'{
"access_token": "string",
"token_type": "bearer",
"expires_in": 0,
"refresh_token": "string"
}Rotate a CLI refresh token and issue a new access token (ADR-014)
Request Body
application/json
"refresh_token""nno-cli"Response Body
application/json
application/json
curl -X POST "https://iam.svc.nno.app/oauth/token" \ -H "Content-Type: application/json" \ -d '{ "grant_type": "refresh_token", "refresh_token": "string", "client_id": "nno-cli" }'{
"access_token": "string",
"token_type": "bearer",
"expires_in": 0,
"refresh_token": "string"
}nullList pending invitations for an organization
Path Parameters
1 <= lengthQuery Parameters
501 <= value <= 100Response Body
application/json
application/json
application/json
curl -X GET "https://iam.svc.nno.app/api/nno/organizations/string/invitations"{
"data": [
{
"id": "string",
"organizationId": "string",
"email": "[email protected]",
"role": "owner",
"status": "string",
"expiresAt": 0,
"inviterId": "string",
"createdAt": 0
}
],
"cursor": "string"
}nullnullInvite a user to an organization
Path Parameters
1 <= lengthRequest Body
application/json
email"member""owner" | "admin" | "member"1 <= lengthResponse Body
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://iam.svc.nno.app/api/nno/organizations/string/invitations" \ -H "Content-Type: application/json" \ -d '{ "email": "[email protected]" }'{
"id": "string",
"organizationId": "string",
"email": "[email protected]",
"role": "owner",
"status": "string",
"expiresAt": 0,
"inviterId": "string",
"createdAt": 0
}nullnullnullnullRevoke a pending invitation
Path Parameters
1 <= length1 <= lengthResponse Body
application/json
application/json
application/json
curl -X DELETE "https://iam.svc.nno.app/api/nno/organizations/string/invitations/string"{
"id": "string"
}nullnullUpdate platform suspension status
Request Body
application/json
1 <= length"suspended" | "active"Response Body
application/json
application/json
application/json
curl -X POST "https://iam.svc.nno.app/internal/platform-status" \ -H "Content-Type: application/json" \ -d '{ "platformId": "string", "status": "suspended" }'nullnullnullcurl -X GET "https://iam.svc.nno.app/api/auth/profile"nullnullnullUpdate the current user's profile
Request Body
application/json
1 <= lengthuriResponse Body
application/json
application/json
application/json
curl -X PATCH "https://iam.svc.nno.app/api/auth/profile" \ -H "Content-Type: application/json" \ -d '{}'nullnullnull