Core Concepts
Heimdall is built around six primitives. Understanding how they relate to each other is the key to a clean integration.
Apps
An App is Heimdall's unit of tenancy. If you are building a SaaS product, each of your customers is an App. If you are building a platform, each workspace or organization is an App.
All data in Heimdall is scoped to an App. Users, roles, permissions, tokens, and audit logs belong to exactly one App and are invisible to all others. This isolation is enforced at the database level, not just in application code.
Key properties
id- Unique identifier (prefixed withapp_)name- URL-safe slug, unique across the accountdisplay_name- Human-readable labelstatus- active, suspended, or archivedcreated_at- ISO 8601 timestamp
Users
A User represents a person or system identity within an App. Users authenticate against their App and receive tokens that carry their roles and permissions.
A single email address can exist across multiple Apps (e.g., a developer who belongs to both their own workspace and a client's workspace). Within a given App, email addresses are unique.
Key properties
id- Unique identifier (prefixed withusr_)email- Unique within the Appdisplay_name- Human-readable labelroles- Array of role names assigned to this userstatus- active, suspended, or deactivatedmetadata- Arbitrary JSON for your application-specific data
Roles
A Role is a named collection of permissions within an App. Instead of assigning individual permissions to each user, you define roles (like "admin", "editor", "viewer") and assign those roles to users.
Roles are App-scoped. An "admin" role in one App is completely independent from an "admin" role in another. Users can hold multiple roles, and their effective permissions are the union of all permissions across their assigned roles.
Key properties
id- Unique identifier (prefixed withrole_)name- URL-safe slug, unique within the Appdisplay_name- Human-readable labelpermissions- Array of permission strings
Permissions
Permissions are strings that represent specific actions in your application. Heimdall does not dictate a schema. You define permission strings that make sense for your domain.
We recommend a colon-separated convention like resource:action, for example: billing:read, users:invite, projects:delete. This keeps things readable and lets you query for all permissions under a resource prefix.
Examples
"billing:read"
"billing:write"
"users:read"
"users:write"
"users:invite"
"projects:read"
"projects:write"
"projects:delete"
"reports:export"Wildcard support is planned for a future release (e.g., billing:* to grant all billing actions).
Tokens
Heimdall issues JWTs signed with RS256. Every token is scoped to an App and carries claims about the authenticated entity (user or service), their roles, and their permissions.
There are two grant types:
- password - For user authentication. Requires email and password.
- client_credentials - For machine-to-machine authentication. Requires client_id and client_secret.
Token payload (decoded)
{
"sub": "usr_01HQ3...",
"app_id": "app_01HQ3...",
"roles": ["admin"],
"permissions": [
"billing:read",
"billing:write",
"users:read",
"users:write",
"users:invite"
],
"iat": 1740048000,
"exp": 1740051600,
"iss": "https://api.productcraft.co/heimdall"
}Access tokens expire after 1 hour by default. Refresh tokens are valid for 30 days. Both values are configurable per App.
Audit Logs
Every state-changing operation in Heimdall produces an audit log entry. This includes user creation, role assignments, permission changes, token issuance, invite generation, and administrative actions.
Audit logs are immutable, App-scoped, and queryable through a dedicated API endpoint. They are retained for 90 days by default, configurable per account.
Example audit entry
{
"id": "aud_01HQ3...",
"app_id": "app_01HQ3...",
"actor_id": "usr_01HQ3...",
"action": "role.assigned",
"target_type": "user",
"target_id": "usr_01HQ4...",
"metadata": {
"role": "editor"
},
"ip_address": "203.0.113.42",
"timestamp": "2026-02-20T10:15:00Z"
}Invites
Invites let existing users bring new users into an App. When you create an invite, Heimdall generates a unique link with configurable expiration and a maximum number of uses. You control which role the invitee receives on acceptance.
Heimdall does not send emails. It gives you the invite payload (including the link and token) and leaves delivery to you. This means you can send invites through email, Slack, SMS, or whatever channel your users prefer.
Invite lifecycle
- 1. Admin creates an invite with a target role and expiry
- 2. Heimdall returns an invite token and link
- 3. You deliver the link to the invitee
- 4. Invitee calls the accept endpoint with the token
- 5. Heimdall creates the user with the specified role
How it all fits together
Account (your ProductCraft account)
└── App (tenant / workspace / organization)
├── Users
│ └── assigned Roles
├── Roles
│ └── contain Permissions
├── Service Credentials (M2M)
│ └── scoped Permissions
├── Invites
└── Audit Logs