Headlessly
Identity

User

Authenticated humans -- founders, team members, and collaborators.

Schema

import { Noun } from 'digital-objects'

export const User = Noun('User', {
  name: 'string!',
  email: 'string!##',
  avatar: 'string',
  role: 'Admin | Member | Viewer',
  status: 'Active | Suspended | Invited',
  invite: 'Invited',
  suspend: 'Suspended',
  activate: 'Activated',
})

Fields

FieldTypeRequiredDescription
namestringYesFull display name
emailstringYesEmail address (unique, indexed)
avatarstringNoURL to profile image
roleenumNoPermission level: Admin, Member, or Viewer
statusenumNoAccount state: Active, Suspended, or Invited

Verbs

VerbEventDescription
createCreatedCreate a new user
updateUpdatedUpdate user fields
deleteDeletedSoft-delete the user
inviteInvitedSend an invite to join the organization
suspendSuspendedTemporarily disable access
activateActivatedActivate a user account

Verb Lifecycle

Every verb follows the full conjugation pattern -- execute, before hook, after hook:

import { User } from '@headlessly/sdk'

// Execute
await User.invite('user_fX9bL5nRd')

// Before hook -- runs before the invite is processed
User.inviting(user => {
  console.log(`About to invite ${user.name}`)
})

// After hook -- runs after the invite completes
User.invited(user => {
  console.log(`${user.name} was invited at ${user.$updatedAt}`)
})

Status State Machine

          invite()
(none) ──────────→ Invited

            activate()│

                   Active ←──── activate()
                      │              ▲
            suspend() │              │
                      ▼              │
                  Suspended ─────────┘

Valid transitions:

FromVerbTo
--inviteInvited
InvitedactivateActive
ActivesuspendSuspended
SuspendedactivateActive

Query Examples

SDK

import { User } from '@headlessly/sdk'

// Find all admins
const admins = await User.find({ role: 'Admin' })

// Get a specific user
const user = await User.get('user_fX9bL5nRd')

// Find suspended users
const suspended = await User.find({ status: 'Suspended' })

MCP

headless.ly/mcp#search
{ "type": "User", "filter": { "role": "Admin" } }
headless.ly/mcp#fetch
{ "type": "User", "id": "user_fX9bL5nRd" }
headless.ly/mcp#do
const admins = await $.User.find({ role: 'Admin' })
await $.User.suspend('user_fX9bL5nRd')

REST

GET https://headless.ly/~acme/users?role=Admin
GET https://headless.ly/~acme/users/user_fX9bL5nRd
POST https://headless.ly/~acme/users/user_fX9bL5nRd/suspend

Event-Driven Patterns

React to user lifecycle events across domains:

import { User } from '@headlessly/sdk'

// When a user is invited, create a welcome activity
User.invited((user, $) => {
  $.Activity.create({
    subject: `Welcome ${user.name}`,
    type: 'Task',
    description: 'Send onboarding materials',
  })
})

// When a user is suspended, revoke their API keys
User.suspended((user, $) => {
  const keys = await $.ApiKey.find({ createdBy: user.$id, status: 'Active' })
  for (const key of keys) {
    await $.ApiKey.revoke(key.$id)
  }
})

On this page