← Heimdall Docs

API Reference

Every Heimdall endpoint documented with request and response examples. All paths are relative to the base URL: https://heimdall.productcraft.co/api

Common headers

  • Authorization: Bearer YOUR_TOKEN for authenticated operations
  • Content-Type: application/json for all request bodies

Error responses

All errors return a consistent shape:

{
  "statusCode": 409,
  "message": "Email already in use",
  "error": "CONFLICT"
}

HTTP status codes follow standard conventions: 400 for validation errors, 401 for missing or invalid credentials, 403 for insufficient permissions, 404 for not found, 409 for conflicts, 410 for expired invites, and 429 for rate limiting.

Permission format

Permissions follow the resource.action format. All permissions are scoped to the App in which the role carrying them is assigned.

Authentication

Register, sign in, refresh tokens, and logout. These endpoints are public and do not require an existing token.

POST
/v1/auth/signup

Create a new account

POST
/v1/auth/signin

Sign in with email/username and password

POST
/v1/auth/refresh

Refresh an access token using a refresh token

POST
/v1/auth/logout

Revoke a session by refresh token

POST
/v1/auth/password/request-reset

Request a password reset code

POST
/v1/auth/password/reset

Reset password with a verification code

Example: Sign up

Request

POST /v1/auth/signup
Content-Type: application/json

{
  "email": "jane@example.com",
  "username": "jane_doe",
  "password": "SecurePassword123",
  "displayName": "Jane Doe"
}

Response

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "refresh_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600
}

Apps

Manage tenants. Each App is an isolated namespace containing its own roles, members, and permissions. The App slug is used as the identifier in URL paths.

POST
/v1/apps

Create a new App (caller becomes owner)

GET
/v1/apps

List Apps the current user belongs to

GET
/v1/apps/:appId

Get App details (requires tenant.read)

PATCH
/v1/apps/:appId

Update App name and metadata (requires tenant.update)

PATCH
/v1/apps/:appId/status

Update App status (requires tenant.update)

DELETE
/v1/apps/:appId

Delete an App permanently (requires tenant.delete)

GET
/v1/apps/:appId/members

List members of an App (requires user.list)

Example: Create an App

Request

POST /v1/apps
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN

{
  "slug": "acme-corp",
  "displayName": "Acme Corporation"
}

Response

{
  "id": "550e8400-e29b-41d4-a716-...",
  "slug": "acme-corp",
  "display_name": "Acme Corporation",
  "status": "active",
  "metadata": {},
  "created_by": "...",
  "created_at": "2026-02-20T10:00:00Z",
  "updated_at": "2026-02-20T10:00:00Z"
}

Roles

Define named collections of permissions within an App. Three system roles (owner, admin, member) are created automatically. You can create additional custom roles.

POST
/v1/apps/:appId/roles

Create a custom role (requires role.create)

GET
/v1/apps/:appId/roles

List all roles in an App (requires role.read)

GET
/v1/apps/:appId/roles/permissions

List all available permissions (requires role.read)

GET
/v1/apps/:appId/roles/:roleName

Get role with its permissions (requires role.read)

PATCH
/v1/apps/:appId/roles/:roleName

Update role name or description (requires role.update)

DELETE
/v1/apps/:appId/roles/:roleName

Delete a custom role (requires role.delete)

PUT
/v1/apps/:appId/roles/:roleName/permissions

Set permissions for a role (requires role.update)

POST
/v1/apps/:appId/roles/assign

Assign a role to a user (requires role.assign)

Example: Create a role

Request

POST /v1/apps/acme-corp/roles
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN

{
  "name": "editor",
  "description": "Can manage content"
}

Response

{
  "id": "...",
  "tenant_id": "...",
  "name": "editor",
  "description": "Can manage content",
  "is_system": false,
  "created_at": "2026-02-20T10:01:00Z",
  "updated_at": "2026-02-20T10:01:00Z"
}

Invites

Generate invite codes to onboard new users into an App with a predefined role. Invites can be restricted to a specific email, limited in uses, and set to expire.

POST
/v1/apps/:appId/invites

Create an invite (requires invite.create)

GET
/v1/apps/:appId/invites

List invites for an App (requires invite.read)

DELETE
/v1/apps/:appId/invites/:inviteId

Revoke an invite (requires invite.revoke)

POST
/v1/invites/accept

Accept an invite using its code (auth required)

Example: Create an invite

Request

POST /v1/apps/acme-corp/invites
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN

{
  "role": "admin",
  "email": "bob@contractor.io",
  "expiresInHours": 72,
  "maxUses": 1
}

Response

{
  "id": "...",
  "tenant_id": "...",
  "email": "bob@contractor.io",
  "code": "dGhpcyBpcyBhIHRlc3Q",
  "role_id": "...",
  "max_uses": 1,
  "use_count": 0,
  "expires_at": "2026-02-23T10:00:00Z",
  "created_by": "...",
  "created_at": "2026-02-20T10:10:00Z"
}

Service Credentials (M2M)

Create and manage service accounts for machine-to-machine authentication. Each credential set is scoped to specific permissions within an App.

POST
/v1/apps/:appId/credentials

Create service credentials (requires m2m.create)

GET
/v1/apps/:appId/credentials

List service credentials (requires m2m.read)

GET
/v1/apps/:appId/credentials/:clientId

Get credential details (requires m2m.read)

POST
/v1/apps/:appId/credentials/:clientId/rotate

Rotate the client secret (requires m2m.rotate_secret)

PUT
/v1/apps/:appId/credentials/:clientId/scopes

Set permission scopes (requires m2m.update)

PATCH
/v1/apps/:appId/credentials/:clientId

Update client status (requires m2m.update)

DELETE
/v1/apps/:appId/credentials/:clientId

Delete credentials (requires m2m.delete)

POST
/v1/oauth/token

Exchange client credentials for an access token (public)

Example: Create service credentials

Request

POST /v1/apps/acme-corp/credentials
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN

{
  "name": "billing-service"
}

Response

{
  "id": "...",
  "client_id": "m2m_a1b2c3d4e5f6...",
  "client_secret": "base64url-encoded-secret",
  "name": "billing-service",
  "created_at": "2026-02-20T10:05:00Z"
}

Audit Logs

Query the immutable audit trail for any App. Every permission change, role assignment, and management action is recorded.

GET
/v1/apps/:appId/audit-logs

List audit log entries (requires audit.read, supports cursor pagination)

Example: Query audit logs

Request

GET /v1/apps/acme-corp/audit-logs?limit=10&cursor=eyJjcmVhdGVkX2F0IjoiMjAyNi0wMi0yMFQxMDoxNTowMFoiLCJpZCI6ImF1ZGl0XzEyMyJ9
Authorization: Bearer YOUR_TOKEN

Response

[
  {
    "id": "...",
    "tenant_id": "...",
    "actor_id": "...",
    "actor_type": "user",
    "action": "role.permissions_changed",
    "resource": "role",
    "resource_id": "...",
    "metadata": {},
    "ip": "203.0.113.42",
    "created_at": "2026-02-20T10:15:00Z"
  }
]

Token Exchange

Exchange a session token for a tenant-scoped token. The returned token includes the user's role and permissions for the specified App, enabling local authorization checks.

POST
/v1/apps/:appId/token

Exchange session token for a tenant-scoped token (auth required)

Example: Get a tenant-scoped token

Request

POST /v1/apps/acme-corp/token
Authorization: Bearer YOUR_SESSION_TOKEN

Response

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "refresh_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600
}

Authorization

Verify a token and check whether the caller has a specific permission. This is a lightweight, public endpoint with no database calls — permissions are read directly from the JWT claims.

POST
/v1/authorize

Verify token and check a permission (public, no auth header needed)

Example: Check a permission

Request

POST /v1/authorize
Content-Type: application/json

{
  "token": "eyJhbGciOiJSUzI1NiIs...",
  "permission": "user.read"
}

Response

{
  "allowed": true,
  "sub": "550e8400-...",
  "tid": "660e8400-...",
  "role": "admin",
  "permissions": [
    "user.read",
    "user.list",
    "tenant.read",
    "audit.read"
  ]
}

Introspection

Inspect the current user's identity, App memberships, and permissions within a specific App.

GET
/v1/introspect

Current user identity and list of Apps

GET
/v1/introspect/apps/:appId

Role and permissions within a specific App

Example: Introspect permissions

Request

GET /v1/introspect/apps/acme-corp
Authorization: Bearer YOUR_TOKEN

Response

{
  "isMember": true,
  "role": { "id": "...", "name": "owner" },
  "permissions": [
    "user.create",
    "user.read",
    "user.update",
    "user.delete",
    "user.list",
    "role.create",
    "role.read",
    "..."
  ]
}

JWKS Endpoint

Verify tokens locally by fetching the public signing keys:

GET /v1/.well-known/jwks.json

Keys are cached for 1 hour (Cache-Control: public, max-age=3600). Use any JWT library (jose, jsonwebtoken, PyJWT, etc.) to verify the RS256 signature.