Skip to main content

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.

List domain templatesGET /api/domain-templates
GET /api/domain-templates is a HAL collection (rel inf:domain-templates) produced by the `domain-builder` Agency’s discover(). Drivers can advertise pre-built domain "templates" for the new-domain UI; none ship today, so the collection is empty.
Response · 200
{
"_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
}
Captured from the API examples test suite; ids and timestamps are normalized.

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.

List domain driversGET /api/domain-drivers
GET /api/domain-drivers is a HAL collection (rel inf:domain-drivers, embedded inf:domain-driver) of the registered authentication DRIVERS — not domain instances. auth: tenant, open: true. Each driver serializes its translated name/group plus flags (loginComponent, loginMethod, usernameParam, passwordParam, managesPassword). The built-in `local` driver sets managesPassword: true (Informer owns the password). Functions are not serialized.
Response · 200
{
"_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"
]
}
}
Captured from the API examples test suite; ids and timestamps are normalized.

GET /api/domain-drivers/{id}

Retrieve a single registered driver by its driver id.

Authentication: Superuser

Pre-blocks: auth.assertSuperuser

Path Parameters:

ParameterTypeDescription
idstringDriver 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.

Get a domain driverGET /api/domain-drivers/local
GET /api/domain-drivers/{id} returns one registered driver by id (rel inf:domain-driver) and adds an inf:scan link. pre: auth.assertSuperuser (403 for non-superusers); 404 when the driver id is not registered. The id is a driver id (e.g. `local`), not a domain instance id.
Response · 200
{
"_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"
}
Captured from the API examples test suite; ids and timestamps are normalized.

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:

ParameterTypeDescription
idstringDriver 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.

Live external operation

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:

ParameterTypeDescription
qstringSearch query
sortstringSort field
limitintegerPage size
startintegerPage 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.

List domainsGET /api/domains
GET /api/domains is the HAL collection of domain INSTANCES (rel inf:domains, embedded inf:domain, query {?sort,limit,start,q}). auth: tenant, open: true. Backed by db.query on the Domain model, it projects only { id, type, name, public } (NOT config). Includes the built-in `local` domain.
Response · 200
{
"_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"
}
}
}
]
}
}
Captured from the API examples test suite; ids and timestamps are normalized.

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.

Get domain membership countsGET /api/domains-content
GET /api/domains-content is a fast raw-SQL summary returning a plain JSON array (rel inf:domains-content-count, NOT a HAL collection envelope) of { id, userCount, teamCount } per domain, including the built-in `local` domain. Use it to show how many users/teams each authentication domain owns.
Response · 200
[
{
"id": "engineering-sso",
"userCount": 0,
"teamCount": 0
},
{
"id": "local",
"userCount": 1,
"teamCount": 0
}
]
Captured from the API examples test suite; ids and timestamps are normalized.

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.

List configured (non-local) domainsGET /api/domains-list
GET /api/domains-list is a fast raw-SQL list of NON-local domains (the query filters `d.type <> 'local'`). Each row carries { id, name, description, type, config, createdAt, scannedAt, updatedAt, default, permissions }, where `default` reflects the tenant’s defaultDomainId. Plain JSON array (rel inf:domains-list), NOT a HAL envelope, with no server-side ordering. A tenant whose only domain is the built-in `local` one returns an empty array.
Response · 200 · empty list (no items in this example)
Captured from the API examples test suite; ids and timestamps are normalized.

GET /api/domains/{id}

Retrieve a single Domain instance.

Authentication: Superuser

Pre-blocks: auth.assertSuperuser, domain.lookup

Path Parameters:

ParameterTypeDescription
idstringDomain 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.

Get a domainGET /api/domains/engineering-sso
GET /api/domains/{id} returns one domain instance (rel inf:domain). pre: auth.assertSuperuser + domain.lookup (404 when missing). The domain’s toHal embeds inf:driver (../../domain-drivers/{type}). The id segment is the domain’s string primary key (slugified from its name on create), NOT a uuid.
Response · 200
{
"_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"
}
}
}
Captured from the API examples test suite; ids and timestamps are normalized.

GET /api/domains/{id}/driver

Resolve the driver backing a specific Domain instance.

Authentication: Superuser

Pre-blocks: auth.assertSuperuser, domain.lookup

Path Parameters:

ParameterTypeDescription
idstringDomain 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.

Get a domain’s driverGET /api/domains/engineering-sso/driver
GET /api/domains/{id}/driver resolves the driver backing a specific domain instance (domain.driver()). pre: auth.assertSuperuser + domain.lookup. No HAL api rel; adds an inf:scan link. Same payload shape as a /domain-drivers/{id} row.
Response · 200
{
"_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"
}
Captured from the API examples test suite; ids and timestamps are normalized.

POST /api/domains

Create an authentication Domain.

Authentication: Superuser

Pre-blocks: auth.assertSuperuser

Request Body:

FieldTypeRequiredDescription
namestringYesDisplay name. The string primary key is slugified from this value
typestringYesA registered driver id (see GET /api/domain-drivers)
descriptionstringNoDescription
configobjectNoDriver-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.

Create a domainPOST /api/domains
POST /api/domains creates an authentication domain. pre: auth.assertSuperuser. Payload is { name (required), description?, type (required), config? } and is stripUnknown — `id` is NOT accepted (the string PK is slugified from `name`). `type` MUST be a registered driver id (the model’s isRegisteredDriver validator 400s otherwise). The handler returns the created domain directly WITHOUT h.response().created(), so it responds 200 (a returned object), NOT 201, and there is NO Location header.
Request body
{
"name": "Partners Directory",
"description": "Authentication realm for external partners.",
"type": "local"
}
Response · 200
{
"_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"
}
}
}
Captured from the API examples test suite; ids and timestamps are normalized.
Status code

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:

ParameterTypeDescription
idstringDomain string primary key

Request Body:

FieldTypeRequiredDescription
idstringYesDomain id (the string primary key is not actually rewritten)
typestringYesA registered driver id
namestringNoUpdate display name
configobjectNoDriver-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.

Update a domainPUT /api/domains/engineering-sso
PUT /api/domains/{id} updates a domain. pre: auth.assertSuperuser + domain.lookup. Payload is { id (required), type (required), config?, name? } and is stripUnknown; the handler calls updateAttributes(payload) (the string PK is not actually rewritten). `type` must be a registered driver id. Returns the updated domain (200). Wrapped in a route transaction.
Request body
{
"id": "engineering-sso",
"type": "local",
"name": "Engineering SSO (updated)",
"config": {
"note": "Updated connection settings."
}
}
Response · 200
{
"_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"
}
}
}
Captured from the API examples test suite; ids and timestamps are normalized.

DELETE /api/domains/{id}

Delete a Domain.

Authentication: Superuser

Pre-blocks: auth.assertSuperuser, domain.lookup

Path Parameters:

ParameterTypeDescription
idstringDomain string primary key

Response:

Responds 200 with an empty body (Informer convention), not 204. The route is wrapped in a transaction.

Delete a domainDELETE /api/domains/partners-directory
DELETE /api/domains/{id} destroys a domain. pre: auth.assertSuperuser + domain.lookup. It cascades to the domain’s Teams and Users (model associations declare onDelete: cascade). Returns 200 with an EMPTY body (Informer convention; NOT 204). Wrapped in a route transaction.
Response · 200 · no body
Captured from the API examples test suite; ids and timestamps are normalized.
Cascade delete

Deleting a Domain cascades to its Teams and Users (the model associations declare onDelete: cascade).