Authentication Domains
Endpoints for managing authentication Domains: the built-in local realm (the
passwords Informer owns) plus any SSO/directory Domains (LDAP, SAML, AD, and so
on) a tenant configures. The same area also exposes the registered Domain
drivers (the auth mechanisms), the new-Domain template catalog, and the
directory scan that imports Users and Teams from an external source.
Two read routes (GET /api/domain-drivers and GET /api/domains) are open to
any authenticated session. Every other route requires Superuser: the
single-Domain and single-driver reads, and all of the create/update/delete
writes, run auth.assertSuperuser and answer 403 for a non-superuser. The
Domain id is a string primary key slugified from the Domain's name on create,
not a UUID.
GET /api/domain-templates
List the Domain templates used to pre-fill the new-Domain UI.
Authentication: Required (no special permission)
Response:
A HAL collection (rel inf:domain-templates) produced by the domain-builder
Agency's discover(). Drivers can advertise pre-built Domain templates here;
none ship today, so the collection is empty.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/domain-templates"
}
},
"items": [
{
"detective": "00000000-0000-4000-8000-000000000001",
"id": "00000000-0000-4000-8000-000000000001:0",
"value": {
"name": "Active Directory",
"group": "LDAP/AD",
"domain": {
"type": "AD",
"defaults": {
"givenName": "givenName",
"familyName": "sn",
"username": "sAMAccountName",
"displayName": "displayName",
"timezone": "tz",
"emailAttribute": "mail"
}
}
}
},
{
"detective": "00000000-0000-4000-8000-000000000002",
"id": "00000000-0000-4000-8000-000000000002:0",
"value": {
"name": "LDAP",
"group": "LDAP/AD",
"domain": {
"type": "LDAP",
"defaults": {
"givenName": "givenName",
"familyName": "sn",
"username": "uid",
"displayName": "displayName",
"timezone": "tz",
"emailAttribute": "mail"
}
}
}
},
{
"detective": "00000000-0000-4000-8000-000000000003",
"id": "00000000-0000-4000-8000-000000000003:0",
"value": {
"name": "SAML",
"group": "SSO",
"domain": {
"type": "SAML",
"defaults": {
"username": "nameID",
"email": "mail",
"displayName": "displayName",
"timezone": "tz",
"givenName": "",
"familyName": ""
}
}
}
},
"… 1 more items (4 total) — omitted from docs"
],
"start": 0,
"count": 4,
"total": 4
}
GET /api/domain-drivers
List the registered Domain drivers (the authentication mechanisms), not the Domain instances.
Authentication: Required (no special permission)
Response:
A HAL collection under _embedded["inf:domain-driver"] (rel
inf:domain-drivers). Each driver serializes its translated name and group
plus its flags (loginComponent, loginMethod, usernameParam,
passwordParam, managesPassword). Driver functions are not serialized. The
built-in local driver sets managesPassword: true because Informer owns the
password.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/domain-drivers"
}
},
"start": 0,
"count": 5,
"total": 5,
"_embedded": {
"inf:domain-driver": [
{
"_links": {
"self": {
"href": "https://informer.example.com/api/domain-drivers/local"
}
},
"id": "local",
"name": "Local",
"group": "local",
"managesPassword": true,
"loginComponent": "localLogin",
"loginMethod": "POST",
"usernameParam": "username",
"passwordParam": "password"
},
{
"_links": {
"self": {
"href": "https://informer.example.com/api/domain-drivers/AD"
}
},
"id": "AD",
"name": "Active Directory",
"group": "LDAP/AD",
"image": "/assets/domains/images/active-directory.svg",
"loginComponent": "adLogin",
"editComponent": {
"tabbedDomainEditor": {
"connectionForm": "adEdit"
}
},
"scanComponent": "adScan",
"loginMethod": "POST",
"usernameParam": "username",
"passwordParam": "password",
"managesPassword": false
},
{
"_links": {
"self": {
"href": "https://informer.example.com/api/domain-drivers/LDAP"
}
},
"id": "LDAP",
"name": "LDAP",
"group": "LDAP/AD",
"loginComponent": "ldapLogin",
"image": "/assets/domains/images/ldap.svg",
"editComponent": {
"tabbedDomainEditor": {
"connectionForm": "ldapEdit"
}
},
"scanComponent": "ldapScan",
"loginMethod": "POST",
"usernameParam": "username",
"passwordParam": "password",
"managesPassword": false
},
"… 2 more items (5 total) — omitted from docs"
]
}
}
GET /api/domain-drivers/{id}
Retrieve a single registered driver by its driver id.
Authentication: Superuser
Pre-blocks: auth.assertSuperuser
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Driver id (e.g. local), not a Domain instance id |
Response:
The driver row, with an added inf:scan link. Answers 404 when the driver id
is not registered.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/domain-drivers/local"
},
"inf:scan": {
"href": "https://informer.example.com/api/domain-drivers/local/_scan"
}
},
"id": "local",
"name": "Local",
"group": "local",
"managesPassword": true,
"loginComponent": "localLogin",
"loginMethod": "POST",
"usernameParam": "username",
"passwordParam": "password"
}
POST /api/domain-drivers/{id}/_scan
Run a directory scan for a driver, importing Users and Teams from the external source.
Authentication: Superuser
Pre-blocks: auth.assertSuperuser
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Driver id to scan with |
This route runs a live external directory scan inside a job task and the route
transaction: the driver's scan() reaches out to an external identity provider
and imports its Users and Teams. The same scan is reachable per Domain instance
via the inf:scan link on GET /api/domains/\{id\}/driver.
A scan contacts a configured external identity provider and mutates Users and Teams in the tenant. Its result depends on the live directory, so no captured example is shown.
GET /api/domains
List the Domain instances configured on the tenant, including the built-in
local Domain.
Authentication: Required (no special permission)
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
q | string | Search query |
sort | string | Sort field |
limit | integer | Page size |
start | integer | Page offset |
Response:
A HAL collection under _embedded["inf:domain"] (rel inf:domains). Each item
projects only { id, type, name, public }; the Domain config is not included
in this collection.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/domains?limit=0&start=0"
}
},
"start": 0,
"count": 2,
"total": 2,
"_embedded": {
"inf:domain": [
{
"_links": {
"self": {
"href": "https://informer.example.com/api/domains/engineering-sso"
}
},
"id": "engineering-sso",
"type": "local",
"name": "Engineering SSO",
"public": {},
"_embedded": {
"inf:driver": {
"_links": {
"self": {
"href": "https://informer.example.com/api/domain-drivers/local"
}
},
"id": "local",
"name": "drivers:domain.local.name",
"group": "local",
"managesPassword": true,
"loginComponent": "localLogin",
"loginMethod": "POST",
"usernameParam": "username",
"passwordParam": "password"
}
}
},
{
"_links": {
"self": {
"href": "https://informer.example.com/api/domains/local"
}
},
"id": "local",
"type": "local",
"name": null,
"public": {},
"_embedded": {
"inf:driver": {
"_links": {
"self": {
"href": "https://informer.example.com/api/domain-drivers/local"
}
},
"id": "local",
"name": "drivers:domain.local.name",
"group": "local",
"managesPassword": true,
"loginComponent": "localLogin",
"loginMethod": "POST",
"usernameParam": "username",
"passwordParam": "password"
}
}
}
]
}
}
GET /api/domains-content
Get per-Domain membership counts.
Authentication: Required (no special permission)
Response:
A plain JSON array (rel inf:domains-content-count), not a HAL collection
envelope. Each entry is { id, userCount, teamCount } and the built-in local
Domain is included. Use it to show how many Users and Teams each authentication
Domain owns. The summary is built with fast raw SQL and has no server-side
ordering.
[
{
"id": "engineering-sso",
"userCount": 0,
"teamCount": 0
},
{
"id": "local",
"userCount": 1,
"teamCount": 0
}
]
GET /api/domains-list
List the configured (non-local) Domains as a flat array.
Authentication: Required (no special permission)
Response:
A plain JSON array (rel inf:domains-list), not a HAL collection envelope. The
query filters out the built-in local Domain (d.type <> 'local'), so a tenant
whose only Domain is local returns an empty array. Each row carries
{ id, name, description, type, config, createdAt, scannedAt, updatedAt, default, permissions },
where default reflects the tenant's defaultDomainId. The list is built with
fast raw SQL and has no server-side ordering.
GET /api/domains/{id}
Retrieve a single Domain instance.
Authentication: Superuser
Pre-blocks: auth.assertSuperuser, domain.lookup
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Domain string primary key (slugified from its name on create) |
Response:
The Domain, whose toHal embeds its resolved driver under inf:driver
(../../domain-drivers/{type}). Answers 404 when the Domain is missing.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/domains/engineering-sso"
}
},
"id": "engineering-sso",
"name": "Engineering SSO",
"description": "Authentication realm for the engineering org.",
"type": "local",
"scannedAt": null,
"config": null,
"public": {},
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"_embedded": {
"inf:driver": {
"_links": {
"self": {
"href": "https://informer.example.com/api/domain-drivers/local"
}
},
"id": "local",
"name": "drivers:domain.local.name",
"group": "local",
"managesPassword": true,
"loginComponent": "localLogin",
"loginMethod": "POST",
"usernameParam": "username",
"passwordParam": "password"
}
}
}
GET /api/domains/{id}/driver
Resolve the driver backing a specific Domain instance.
Authentication: Superuser
Pre-blocks: auth.assertSuperuser, domain.lookup
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Domain string primary key |
Response:
The resolved driver (domain.driver()), with an added inf:scan link. There is
no HAL api rel for this route; the payload shape matches a
GET /api/domain-drivers/\{id\} row.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/domains/engineering-sso/driver"
},
"inf:scan": {
"href": "https://informer.example.com/api/domains/engineering-sso/driver/_scan"
}
},
"id": "local",
"name": "Local",
"group": "local",
"managesPassword": true,
"loginComponent": "localLogin",
"loginMethod": "POST",
"usernameParam": "username",
"passwordParam": "password"
}
POST /api/domains
Create an authentication Domain.
Authentication: Superuser
Pre-blocks: auth.assertSuperuser
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name. The string primary key is slugified from this value |
type | string | Yes | A registered driver id (see GET /api/domain-drivers) |
description | string | No | Description |
config | object | No | Driver-specific connection settings |
The payload is stripUnknown, so id is not accepted (the primary key is
slugified from name). type must be a registered driver id, or the model's
isRegisteredDriver validator answers 400.
Example Request:
{
"name": "Partners Directory",
"description": "Authentication realm for external partners.",
"type": "local"
}
Response:
The handler returns the created Domain directly (no h.response().created()),
so it responds 200 with the Domain object, not 201, and there is no
Location header. The Domain's toHal embeds its resolved driver under
inf:driver. The route is wrapped in a transaction.
{
"name": "Partners Directory",
"description": "Authentication realm for external partners.",
"type": "local"
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/domains"
}
},
"public": {},
"tenant": "acme",
"id": "partners-directory",
"name": "Partners Directory",
"description": "Authentication realm for external partners.",
"type": "local",
"updatedAt": "2026-01-15T16:20:00.000Z",
"createdAt": "2026-01-15T16:20:00.000Z",
"config": null,
"scannedAt": null,
"_embedded": {
"inf:driver": {
"_links": {
"self": {
"href": "https://informer.example.com/domain-drivers/local"
}
},
"id": "local",
"name": "drivers:domain.local.name",
"group": "local",
"managesPassword": true,
"loginComponent": "localLogin",
"loginMethod": "POST",
"usernameParam": "username",
"passwordParam": "password"
}
}
}
Unlike most create routes, this one answers 200 (a returned object) rather
than 201 Created, and omits the Location header.
PUT /api/domains/{id}
Update a Domain.
Authentication: Superuser
Pre-blocks: auth.assertSuperuser, domain.lookup
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Domain string primary key |
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Domain id (the string primary key is not actually rewritten) |
type | string | Yes | A registered driver id |
name | string | No | Update display name |
config | object | No | Driver-specific connection settings |
The payload is stripUnknown. The handler calls updateAttributes(payload), so
although id is required in the schema the string primary key is not rewritten.
type must be a registered driver id. The route is wrapped in a transaction.
Example Request:
{
"id": "engineering-sso",
"type": "local",
"name": "Engineering SSO (updated)",
"config": { "note": "Updated connection settings." }
}
Response:
Responds 200 with the updated Domain.
{
"id": "engineering-sso",
"type": "local",
"name": "Engineering SSO (updated)",
"config": {
"note": "Updated connection settings."
}
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/domains/engineering-sso"
}
},
"id": "engineering-sso",
"name": "Engineering SSO (updated)",
"description": "Authentication realm for the engineering org.",
"type": "local",
"scannedAt": null,
"config": {
"note": "Updated connection settings."
},
"public": {},
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"_embedded": {
"inf:driver": {
"_links": {
"self": {
"href": "https://informer.example.com/api/domain-drivers/local"
}
},
"id": "local",
"name": "drivers:domain.local.name",
"group": "local",
"managesPassword": true,
"loginComponent": "localLogin",
"loginMethod": "POST",
"usernameParam": "username",
"passwordParam": "password"
}
}
}
DELETE /api/domains/{id}
Delete a Domain.
Authentication: Superuser
Pre-blocks: auth.assertSuperuser, domain.lookup
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Domain string primary key |
Response:
Responds 200 with an empty body (Informer convention), not 204. The route is
wrapped in a transaction.
Deleting a Domain cascades to its Teams and Users (the model associations
declare onDelete: cascade).