← 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. Roles, permissions, tokens, and audit logs belong to exactly one App and are invisible to all others. This isolation is enforced automatically on every query.

When you create an App, three system roles are provisioned automatically: owner, admin, and member. The creator is assigned the owner role with full permissions.

Key properties

  • id - Unique identifier (UUID)
  • slug - URL-safe identifier, globally unique
  • display_name - Human-readable label
  • status - active, suspended, or archived
  • metadata - Arbitrary JSON for your application-specific data

Users

A User represents a person who can authenticate with Heimdall. Users register once and can be members of multiple Apps. Within each App, a user holds a specific role that determines their permissions.

Users authenticate with email/username and password, or through social login providers (Google, GitHub). A single person can belong to many Apps — for example, a developer who belongs to both their own workspace and a client's workspace.

Key properties

  • id - Unique identifier (UUID)
  • email - Primary email address
  • username - Unique username
  • display_name - Human-readable label
  • 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. Each user holds exactly one role per App, and their effective permissions are the permissions of that role.

Every App comes with three system roles that cannot be deleted:

  • owner — full permissions on all resources
  • admin — management permissions without destructive capabilities
  • member — read-only access to basic information

You can create additional custom roles with any combination of the available permissions.

Permissions

Permissions are strings that represent specific actions. Heimdall uses a dot-separated convention: resource.action. All permissions are scoped to the App in which the role carrying them is assigned.

Available permissions

user.create    user.read    user.update    user.delete    user.list
role.create    role.read    role.update    role.delete    role.assign    role.revoke
tenant.read    tenant.update    tenant.delete
invite.create  invite.read  invite.revoke
m2m.create     m2m.read     m2m.update     m2m.delete     m2m.rotate_secret
audit.read

Assign permissions to roles, then assign roles to users. Heimdall checks permissions on every API call that modifies App-scoped data.

Tokens

Heimdall issues JWTs signed with RS256. Every token carries claims about the authenticated entity (user or service).

There are two types of authentication:

  • User tokens — issued via signup, signin, or social login. Include the user's identity.
  • M2M tokens — issued via client credentials grant. Include the client's scoped permissions.

Access tokens expire after 1 hour by default. Refresh tokens are valid for 30 days. Verify tokens locally using the JWKS endpoint or via the introspection API.

JWKS endpoint

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

Cache the keys locally and verify tokens without a network round-trip. The endpoint returns a Cache-Control header with a 1-hour max-age.

Audit Logs

Every state-changing operation in Heimdall produces an audit log entry. This includes tenant updates, role assignments, permission changes, invite generation, and M2M client management.

Audit logs are immutable, App-scoped, and queryable through a dedicated API endpoint. Each entry records the actor, action, affected resource, and client IP address.

Example audit entry

{
  "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"
}

Invites

Invites let existing users bring new users into an App. When you create an invite, Heimdall generates a unique code with configurable expiration and a maximum number of uses. You control which role the invitee receives on acceptance.

Heimdall returns the invite code and leaves delivery to you. Send invites through email, Slack, SMS, or whatever channel your users prefer. The invitee must have a Heimdall account (signed up via the auth endpoints) before accepting the invite.

Invite lifecycle

  • 1. An admin creates an invite with a target role and optional expiry
  • 2. Heimdall returns an invite code
  • 3. You deliver the code to the invitee
  • 4. The invitee signs up (if they haven't already)
  • 5. The invitee calls the accept endpoint with the code
  • 6. Heimdall adds the user to the App with the specified role

How it all fits together

User (registers once, belongs to many Apps)
  └── App (tenant / workspace / organization)
        ├── Members
        │     └── each holds one Role
        ├── Roles
        │     └── contain Permissions
        ├── Service Credentials (M2M)
        │     └── scoped Permissions
        ├── Invites
        └── Audit Logs