← Heimdall Docs

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 with app_)
  • name - URL-safe slug, unique across the account
  • display_name - Human-readable label
  • status - active, suspended, or archived
  • created_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 with usr_)
  • email - Unique within the App
  • display_name - Human-readable label
  • roles - Array of role names assigned to this user
  • status - active, suspended, or deactivated
  • metadata - 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 with role_)
  • name - URL-safe slug, unique within the App
  • display_name - Human-readable label
  • permissions - 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