Skip to main content

Users API

User object

Every user has a stable internal id (UUID). The did field is null for unclaimed users who have not yet activated their account by scanning a QR code with the Vaultys wallet. Unclaimed users can be created via:

  • Email invitations: Admin sends email link, user scans QR to claim
  • Entra ID sync: Microsoft Graph sync, user gets QR link to claim
{
"id": "018e4f2a-...",
"did": "did:vaultys:z6MkAlice...",
"name": "Alice Smith",
"email": "alice@acme.com",
"isOwner": false,
"isAdmin": true,
"role": "admin",
"registeredAt": "2026-01-10T09:00:00Z",
"entraId": null,
"claimedAt": null
}

For a user provisioned from Entra ID but not yet claimed, did is null and entraId holds the Entra object ID:

{
"id": "018e4f2a-...",
"did": null,
"name": "Bob Jones",
"email": "bob@acme.com",
"isOwner": false,
"isAdmin": false,
"role": "member",
"registeredAt": "2026-03-01T08:00:00Z",
"entraId": "aad-object-id-...",
"claimedAt": null
}

Get current user

GET /api/users/me

Auth: Required.

Returns the authenticated user's profile, realm memberships, and capability grants.

Response

{
"user": {
"id": "018e4f2a-...",
"did": "did:vaultys:z6MkAlice...",
"name": "Alice Smith",
"email": "alice@acme.com",
"isAdmin": true,
"registeredAt": "2026-01-10T09:00:00Z"
},
"realms": [
{
"id": "realm_eng",
"name": "Engineering",
"slug": "eng",
"color": "#3b82f6",
"role": "admin"
}
],
"grants": [
{
"id": "grant_01HZ...",
"agentDid": null,
"capabilities": ["api_call", "file_access"],
"expiresAt": null,
"createdAt": "2026-01-15T10:00:00Z"
}
]
}

List users

GET /api/users

Auth: Admin only.

Query parameters

ParameterTypeDescription
qstringSearch by name, email, or DID
rolestringFilter by role (owner, admin, manager, operator, member)
realmstringFilter by realm ID or slug
isAdminbooleantrue = global admins only, false = non-admins only
hasAccountbooleantrue = claimed users (did not null), false = unclaimed
pageintegerPage number (default 1)
pageSizeintegerItems per page (default 20, max 100)
sortBystringname, email, or registeredAt
sortDirstringasc or desc

Use hasAccount=false to list only Entra-provisioned users who have not yet claimed their account (equivalent to the Unclaimed tab in the control-plane UI).

Response

{
"users": [
{
"id": "018e4f2a-...",
"did": "did:vaultys:z6MkAlice...",
"name": "Alice Smith",
"email": "alice@acme.com",
"isAdmin": true,
"role": "admin",
"registeredAt": "2026-01-10T09:00:00Z",
"entraId": null,
"claimedAt": null,
"realms": [
{
"id": "realm_eng",
"name": "Engineering",
"slug": "eng",
"color": "#3b82f6",
"isPrimary": true
}
],
"grants": []
}
],
"total": 24,
"page": 1,
"pageSize": 20,
"totalPages": 2
}

Get a user by DID

GET /api/users/:did

Auth: Admin only.

Returns full profile including grants. :did must be URL-encoded.

Update a user

PATCH /api/users/:did

Auth: Owner only.

{
"name": "Alice M. Smith",
"email": "alice@acme.com",
"role": "manager",
"reportsTo": "018e4f2b-...",
"description": "Lead of the platform team"
}

All fields are optional; omitted fields are unchanged.

Delete a user

DELETE /api/users/:did

Auth: Owner only. Cannot delete yourself.

Unclaimed (Entra-provisioned) users

Users imported from Entra ID have no DID until they claim their account. Use the /api/users/unclaimed/:id endpoints to manage them.

Get an unclaimed user

GET /api/users/unclaimed/:id

Auth: Admin only.

Returns 404 if the user does not exist or has already claimed their account (use GET /api/users/:did for claimed users).

{
"id": "018e4f2a-...",
"did": null,
"name": "Bob Jones",
"email": "bob@acme.com",
"role": "member",
"reportsTo": null,
"description": null,
"registeredAt": "2026-03-01T08:00:00Z",
"entraId": "aad-object-id-...",
"claimedAt": null,
"realms": [
{
"id": "realm_ops",
"name": "Operations",
"slug": "ops",
"color": "#f59e0b",
"isPrimary": false
}
]
}

Update an unclaimed user

PATCH /api/users/unclaimed/:id

Auth: Admin only.

Same fields as PATCH /api/users/:did except reportsTo is accepted as-is.

Delete an unclaimed user

DELETE /api/users/unclaimed/:id

Auth: Admin only.

Removes the provisioned record. The user can be re-added by running another Entra sync.

Generate a claim QR code

POST /api/server/entra/send-qr

Auth: Admin only.

Creates a time-limited P2P registration session for an unclaimed user and optionally sends it by email.

{
"userId": "018e4f2a-...",
"sendByEmail": true
}

Response

{
"qrUrl": "https://wallet.vaultys.net/#...",
"token": "abc123...",
"serverDid": "did:vaultys:z6MkServer...",
"emailSent": true
}

Poll GET /api/user/listen/:token to track the claim status:

status valueMeaning
-1Session pending — wallet has not connected yet
2Success — user claimed their account
-2Failed or expired

Once status is 2, the user's did, public_key, and claimed_at fields are set and they can log in normally.

Capability grants

Grants allow non-admin users to send intents with specific capabilities. Grants require the user to have a VaultysID (did must not be null).

Create a grant

POST /api/users/:did/grants

Auth: Global admin.

{
"agentDid": "did:vaultys:z6MkAgent...",
"capabilities": ["api_call", "file_access"],
"expiresAt": "2026-12-31T23:59:59Z"
}

Set agentDid to null to grant access to all agents.

List grants for a user

GET /api/users/:did/grants

Auth: Admin only.

Revoke a grant

DELETE /api/users/:did/grants/:grantId

Auth: Admin only.

The associated delegation certificate is immediately invalidated.

Email Invitations

Send invitations to new users via email. Invitations create unclaimed user records and generate unique registration links.

Send email invitation

POST /api/users/invite/email

Auth: Owner only.

Request:

{
"email": "alice@company.com",
"name": "Alice Johnson",
"role": "manager"
}

Response:

{
"token": "uuid-string",
"userId": "uuid-string"
}

Creates an unclaimed user record with the provided name, email, and role. If an active invitation already exists for this email, the old token is deleted and a new one is generated.

Get invitation details

GET /api/invitations/:token

Auth: Public (no authentication required).

Returns invitation details for a user who received an email link.

Response:

{
"email": "alice@company.com",
"name": "Alice Johnson",
"role": "manager"
}

Errors:

  • 404: Token not found or expired (7+ days old)

Generate QR from invitation

POST /api/users/invite/from-email

Auth: Public (no authentication required).

Called by the invitation acceptance page to generate a QR code for wallet scanning.

Request:

{
"token": "uuid-string"
}

Response:

{
"qrUrl": "https://wallet.vaultys.net/#...",
"connectionString": "...",
"inviteToken": "cert-connection-token",
"serverDid": "did:vaultys:..."
}

The inviteToken is used for polling the registration status via /api/user/listen/:inviteToken.

Errors:

  • 404: Token not found or expired
  • 500: Failed to generate registration certificate

Delete invitation

POST /api/invitations/:token/delete

Auth: Public (no authentication required).

Deletes an invitation token after successful registration. Called automatically when the user completes the wallet connection.

Response:

{
"success": true
}

Direct QR Invitations

Generate a QR code immediately without requiring an email.

Generate QR code

GET /api/users/invite

Auth: Owner only.

Creates a registration certificate and P2P session. Returns a connection string for generating a QR code.

Response:

{
"connectionString": "...",
"token": "cert-token-for-polling",
"key": "cert-key",
"serverDid": "did:vaultys:..."
}

The QR code format is:

{walletUrl}/#${connectionString}&protocol=p2p&service=auth&did=${serverDid}

Poll /api/user/listen/:token to check registration status.