Every operation exists twice, with the same permission checks: in REST (described by OpenAPI) and in GraphQL. Both are served by every kernoid process with the api role, for the instance the request’s host belongs to.
Where
Description
REST
/v1/…
OpenAPI 3.1 at /openapi.json on every instance – also here
Both files are generated from the code (kernoid openapi, kernoid graphql-schema); CI checks that this site has the current ones.
Requests
The instance is chosen by the host: send requests to a domain of the instance (behind a reverse proxy, the forwarded host counts only from trusted proxies).
Authentication:Authorization: Bearer <session token> – the token from POST /v1/sessions. The session’s account acts; what it may do follows from its roles.
In a browser, the session can live in an HttpOnly cookie instead ("cookie": true at login, see Login); a change made with the cookie needs the header kernoid-csrf and an Origin of the instance, otherwise 403 with urn:kernoid:problem:csrf.
The tenant administered is the account’s own; instance administrators name another one with the header kernoid-tenant: <tenant id>.
Request bodies are JSON, at most 2 MiB; a request may take 30 seconds.
Every response carries an x-request-id, which the server’s log carries as well.
Errors
REST errors are problem details (RFC 9457, application/problem+json): type, title, status, detail. Internal details never appear in a response – they go to the log, findable by the request id.
Status
Means
400
the request is not acceptable: a malformed value, or a rule refuses it (e.g. revoking the last tenant_admin)
401
no valid session, or wrong credentials; with type urn:kernoid:problem:login-required a fresh login is needed, with urn:kernoid:problem:mfa-required a second factor
403
the session is valid, but the permission named in detail is missing; or the account is deactivated
404
it does not exist – or it belongs to a tenant the caller cannot see
409
it exists already, or a domain or address is in use
429
too many failed attempts; see Retry-After
503
PostgreSQL or object storage is not reachable
GraphQL errors carry the same as a code in extensions.code: UNAUTHENTICATED, LOGIN_REQUIRED, MFA_REQUIRED, FORBIDDEN, NOT_FOUND, CONFLICT, BAD_REQUEST, TOO_MANY_ATTEMPTS (with retryAfter), UNAVAILABLE. Queries are limited in depth (16) and complexity (1000).
GraphQL
Queries read, mutations change; names follow the REST operations in camelCase – GET /v1/groups is groups, POST /v1/groups is addGroup, PATCH /v1/accounts/{id} is updateAccount, PUT /v1/roles/{role}/{kind}/{id} is grantRole. Several mutations in one request run one after the other:
Stops sharing a calendar or address book with someone.
GET
/v1/account/consents
The applications one’s own account agreed to, with the scopes.
DELETE
/v1/account/consents/{application}
Withdraws one’s own consent to an application, and the application’s tokens – offline access included.
GET
/v1/account/factors
The second factors of one’s own account – for the login UI to ask for one, or to have one set up.
POST
/v1/account/idp-links
Starts linking another provider to one’s own account (RFC 0003, 3); answers with where to send the browser.
GET
/v1/account/mail/access
Who may use one’s own mail.
PUT
/v1/account/mail/access
Shares one’s own mail – the whole store or one mailbox – with an account or a group of one’s tenant.
DELETE
/v1/account/mail/access
Stops sharing one’s own mail with someone.
GET
/v1/account/mail/shared
The mail shared with one’s own account: other accounts’ stores one may use.
GET
/v1/account/passkeys
The passkeys of one’s own account.
POST
/v1/account/passkeys/finish
Finishes registering a passkey.
POST
/v1/account/passkeys/start
Starts registering a passkey for one’s own account (needs a recent session).
DELETE
/v1/account/passkeys/{id}
Removes a passkey of one’s own account.
PUT
/v1/account/password
Changes the password of one’s own account: needs a recent session and the current password. Every other session of the account ends.
POST
/v1/account/recovery-codes
Generates new recovery codes for one’s own account (needs a recent session); earlier ones stop working.
POST
/v1/account/totp
Proposes a TOTP secret for one’s own account (needs a recent session).
DELETE
/v1/account/totp
Removes TOTP from one’s own account.
POST
/v1/account/totp/confirm
Confirms the proposed TOTP secret with a first code.
Login UI
Method
Path
What it does
GET
/v1/auth-requests/{id}
A pending auth request – for the login UI to show who is asking.
POST
/v1/auth-requests/{id}
Finishes an auth request with the session of the bearer token; answers with the URL to send the browser to. If the application needs consent, the answer is 403 with type urn:kernoid:problem:consent-required: show what it asks for, and finish again with {"consent": true}.
POST
/v1/auth-requests/{id}/decline
Ends an auth request without a login – no session for prompt=none, or the person declined; answers with the URL that tells the application.
POST
/v1/idp-logins
Starts a login at an identity provider; answers with where to send the browser.
POST
/v1/idp-logins/link
Links the provider login of a proposal to the account of the session – the person logged in to it to prove it is theirs; from then on the provider logs in as that account.
POST
/v1/idp-logins/proposal
What a provider login that found an account with its verified address proposes (RFC 0003, 3): log in to that account to link it – or, where registration is open, create a separate one. Does not use the code up.
POST
/v1/idp-logins/register
Creates a separate account for the provider login of a proposal instead of linking it, where registration is open, and logs it in.
POST
/v1/idp-logins/session
Exchanges the result of a login at an identity provider for a session – once.
GET
/v1/login-options
Where an address logs in: at an identity provider its domain is routed to, or with kernoid’s own login. Says nothing about whether the account exists.
GET
/v1/login-providers
The providers offered to everyone on the login page (“Sign in with …”).
GET
/v1/registration
What registering offers at this instance; the providers to register with are at /v1/login-providers.
POST
/v1/registrations
Starts a registration: checks it, refuses what is taken (409: log in instead), and sends a code to the email address. Nothing is stored as an account before the code is confirmed.
POST
/v1/registrations/{id}/confirm
Finishes a registration with the code from the message: the account is created.
The tenant
Method
Path
What it does
GET
/v1/tenant
The tenant administered (needs directory.read).
PATCH
/v1/tenant
Renames the tenant (needs tenant.manage).
POST
/v1/tenant/domains
Claims a mail domain (needs domains.manage); answers with the TXT record that proves it.
DELETE
/v1/tenant/domains/{domain}
Removes a mail domain – unless addresses use it (needs domains.manage).
POST
/v1/tenant/domains/{domain}/verify
Verifies a claimed domain by its TXT record (needs domains.manage), or marks it verified without DNS (trust, needs tenants.manage).
GET
/v1/tenant/login-policy
How the tenant’s accounts log in (needs directory.read).
PUT
/v1/tenant/login-policy
Sets how the tenant’s accounts log in (needs policies.manage).
GET
/v1/tenant/mail-dns
The tenant’s verified domains as mail domains of kernoid: the DNS records to publish (MX, SPF, DKIM, DMARC) and whether they are published (needs directory.read; reads DNS, changes nothing).
PUT
/v1/tenant/modules/{module}
Switches a module on or off for the tenant (needs tenant.manage). The module must be compiled in; whether it runs is decided by the installation’s configuration.
GET
/v1/tenant/permissions
The permissions of the caller for the tenant administered – for a UI to show what it may do.
PUT
/v1/tenant/policy
Sets whether every login to an application of the tenant needs a second factor, and how long accounts without one may still log in (needs policies.manage).
GET
/v1/tenant/registration
Whether people may register with the tenant, and on which domains (needs directory.read).
PUT
/v1/tenant/registration
Opens or closes registration, and names the domains new accounts may take an address on (needs policies.manage).
GET
/v1/tenant/service-policies
The tenant’s rules for each service (needs directory.read).
PUT
/v1/tenant/service-policies/{service}
Sets who gets a service when an account is created, and whether it needs approval (needs policies.manage).
Accounts
Method
Path
What it does
GET
/v1/accounts
The tenant’s accounts (needs directory.read).
POST
/v1/accounts
Creates an account (needs accounts.manage).
GET
/v1/accounts/{id}
One account (needs directory.read).
PATCH
/v1/accounts/{id}
Changes an account’s display name, login name or active state (needs accounts.manage).
PUT
/v1/accounts/{id}/aliases/{address}
Adds an alias on a verified domain of the tenant (needs accounts.manage).
DELETE
/v1/accounts/{id}/aliases/{address}
Removes an alias; the login name cannot be removed (needs accounts.manage).
GET
/v1/accounts/{id}/consents
The applications an account agreed to (needs accounts.manage).
DELETE
/v1/accounts/{id}/consents/{application}
Withdraws an account’s consent to an application and the application’s tokens for it (needs accounts.manage and every permission the account holds).
PUT
/v1/accounts/{id}/contact
Sets an account’s contact address (needs accounts.manage).
GET
/v1/accounts/{id}/links
The external identities linked to an account (needs directory.read).
DELETE
/v1/accounts/{id}/links/{idp}/{subject}
Unlinks an external identity from an account (needs accounts.manage).
PUT
/v1/accounts/{id}/password
Sets an account’s password and ends its sessions (needs accounts.manage and every permission the account holds).
DELETE
/v1/accounts/{id}/second-factors
Removes all TOTP, passkeys and recovery codes of an account (needs accounts.manage and every permission the account holds).
GET
/v1/accounts/{id}/services
The services of an account (needs directory.read).
PUT
/v1/accounts/{id}/services/{service}
Grants a service – also one an account requested (needs accounts.manage); mail only to an account with an address of the tenant.
DELETE
/v1/accounts/{id}/services/{service}
Takes a service away (needs accounts.manage); a mail store stays until the account is removed.
GET
/v1/accounts/{id}/sessions
The account’s valid sessions (needs accounts.manage).
DELETE
/v1/accounts/{id}/sessions/{session}
Ends one session of the account (needs accounts.manage and every permission the account holds).
PUT
/v1/accounts/{id}/username
Gives an account a username, or takes it away (needs accounts.manage; the tenant must use usernames).
POST
/v1/external-accounts
Creates an account without an address of the tenant: for logins only – and files, if granted (needs accounts.manage).
Mail
Method
Path
What it does
GET
/v1/accounts/{id}/mail
An account’s mail: usage, quota and mailboxes (needs accounts.manage).
GET
/v1/accounts/{id}/mail/access
Who may use an account’s mail (needs accounts.manage).
PUT
/v1/accounts/{id}/mail/access
Grants an account or a group of the tenant access to an account’s mail, replacing an earlier grant there (needs accounts.manage).
DELETE
/v1/accounts/{id}/mail/access
Removes a grant on an account’s mail (needs accounts.manage).
POST
/v1/accounts/{id}/mail/import
Stores a message (RFC 5322, the request body) in a mailbox of an account (needs accounts.manage).
GET
/v1/accounts/{id}/mail/mailboxes/{mailbox}/mbox
A mailbox of an account (by id or role) as mbox, with its keywords (needs accounts.manage).
POST
/v1/accounts/{id}/mail/mailboxes/{mailbox}/mbox
Stores the messages of an mbox (the request body, up to 2 MiB; larger ones with the CLI) in a mailbox of an account (by id or role), with their keywords (needs accounts.manage).
PUT
/v1/accounts/{id}/mail/quota
Sets an account’s mail quota; null for the tenant’s default (needs accounts.manage).
GET
/v1/accounts/{id}/mail/sieve
An account’s Sieve scripts (needs accounts.manage).
PUT
/v1/accounts/{id}/mail/sieve-active
Chooses the Sieve script that runs; null for none (needs accounts.manage).
PUT
/v1/accounts/{id}/mail/sieve/{name}
Saves a Sieve script, checked first (needs accounts.manage).
DELETE
/v1/accounts/{id}/mail/sieve/{name}
Removes a Sieve script (needs accounts.manage).
GET
/v1/instance/mail-queue
The queue of outgoing mail of the instance’s tenants, newest first (needs instance.read).
POST
/v1/shared-mailboxes
Creates a shared mailbox: an account nobody logs in as, used through granted access (needs accounts.manage).
GET
/v1/tenant/mail-quota
The tenant’s default mail quota (needs directory.read).
PUT
/v1/tenant/mail-quota
Sets the tenant’s default mail quota, for every account without its own; null for no limit (needs tenant.manage).
GET
/v1/tenant/mail-retention
The tenant’s mail retention (needs directory.read).
PUT
/v1/tenant/mail-retention
Sets how many days emails stay in the accounts’ Trash and Junk; null keeps them (needs tenant.manage).
Groups
Method
Path
What it does
GET
/v1/groups
The tenant’s groups with their members (needs directory.read).
POST
/v1/groups
Creates a group (needs groups.manage).
DELETE
/v1/groups/{id}
Removes a group; refused while it holds roles (needs groups.manage).
PATCH
/v1/groups/{id}
Renames a group (needs groups.manage).
PUT
/v1/groups/{id}/addresses/{address}
Gives a group an address on a verified domain of the tenant; mail to it goes to every member (needs groups.manage).
DELETE
/v1/groups/{id}/addresses/{address}
Removes an address of a group (needs groups.manage).
PUT
/v1/groups/{id}/members/{account}
Adds an account to a group (needs groups.manage, and for a group holding roles the permission to assign them).
DELETE
/v1/groups/{id}/members/{account}
Removes an account from a group (needs the same as adding one).
Roles
Method
Path
What it does
GET
/v1/roles
The roles there are, and who holds which in the tenant (needs directory.read).
PUT
/v1/roles/{role}
Defines a role of the tenant’s own, or changes its permissions (needs roles.manage).
DELETE
/v1/roles/{role}
Deletes a role of the tenant’s own, once nobody holds it (needs roles.manage).
PUT
/v1/roles/{role}/{kind}/{id}
Assigns a role to an account or a group of the tenant (needs roles.manage).
DELETE
/v1/roles/{role}/{kind}/{id}
Takes a role back; the tenant’s last tenant_admin assignment cannot be revoked (needs roles.manage).
Projects
Method
Path
What it does
GET
/v1/granted-projects
The projects other tenants have granted to this one, with the roles it may give (needs directory.read).
GET
/v1/projects
The tenant’s projects with their roles (needs directory.read).
POST
/v1/projects
Creates a project (needs projects.manage).
GET
/v1/projects/{id}
One project (needs directory.read).
DELETE
/v1/projects/{id}
Removes a project – once it has no applications and nobody holds its roles (needs projects.manage).
PATCH
/v1/projects/{id}
Renames a project or changes its settings (needs projects.manage).
GET
/v1/projects/{id}/authorizations
Who holds which role of the project – for a granted project only the tenant’s own (needs directory.read).
Takes a project role back (needs authorizations.manage).
GET
/v1/projects/{id}/grants
The tenants the project is granted to (needs directory.read).
PUT
/v1/projects/{id}/grants/{tenant}
Grants the project to another tenant of the instance – named by its id or a verified mail domain – with some of its roles, or changes them (needs projects.manage).
DELETE
/v1/projects/{id}/grants/{tenant}
Takes a grant back, once that tenant holds none of the project’s roles (needs projects.manage).
PUT
/v1/projects/{id}/roles/{key}
Adds a role to the project, or changes its display name and group (needs projects.manage).
DELETE
/v1/projects/{id}/roles/{key}
Removes a role from the project, once nobody holds it (needs projects.manage).
Applications
Method
Path
What it does
GET
/v1/applications
The tenant’s applications (needs directory.read).
POST
/v1/applications
Registers an application in a project of the tenant (needs applications.manage).
GET
/v1/applications/{id}
One application (needs directory.read).
PATCH
/v1/applications/{id}
Changes an application (needs applications.manage).
PUT
/v1/applications/{id}/options
Sets the application’s options: token format, offline access, consent, logout notifications (needs applications.manage).
PUT
/v1/applications/{id}/policy
Sets whether logins to the application need a second factor, on top of the tenant’s policy (needs applications.manage).
POST
/v1/applications/{id}/secret
Replaces the secret of a confidential application (needs applications.manage).
Identity providers
Method
Path
What it does
GET
/v1/identity-providers
The tenant’s identity providers, and those of the instance that serve every tenant (needs directory.read).
POST
/v1/identity-providers
Connects an identity provider (needs identity_providers.manage; for the whole instance, instance.manage). Register the redirect_uri from the answer of GET /v1/identity-providers/{id} at the provider.
GET
/v1/identity-providers/{id}
One identity provider (needs directory.read).
DELETE
/v1/identity-providers/{id}
Removes an identity provider, once no account is linked to it (needs identity_providers.manage).
PATCH
/v1/identity-providers/{id}
Changes an identity provider’s settings, or switches it off and on (needs identity_providers.manage).
POST
/v1/identity-providers/{id}/check
Fetches the provider’s discovery document and keys: whether kernoid reaches it as configured (needs identity_providers.manage).
PUT
/v1/identity-providers/{id}/secret
Sets the client secret (needs identity_providers.manage).
DELETE
/v1/identity-providers/{id}/secret
Removes the client secret – for providers that take PKCE alone (needs identity_providers.manage).
Provisioning (SCIM tokens)
Method
Path
What it does
GET
/v1/scim-tokens
The tenant’s SCIM tokens (needs directory.read).
POST
/v1/scim-tokens
A new SCIM token for the tenant’s directory (needs provisioning.manage).
DELETE
/v1/scim-tokens/{id}
Revokes a SCIM token (needs provisioning.manage).
The instance
Method
Path
What it does
GET
/v1/instance
The instance reached under the host of this request.
PATCH
/v1/instance
Renames the instance (needs instance.manage).
GET
/v1/instance/details
The instance with all its domains (needs instance.read).
PUT
/v1/instance/domains/{domain}
Reaches the instance under another domain as well (needs instance.manage).
DELETE
/v1/instance/domains/{domain}
Removes a domain; not the primary one (needs instance.manage).
PUT
/v1/instance/login-ui
Sends logins to a login UI of the instance’s own, which may then call the API from its origin (needs instance.manage).
PUT
/v1/instance/passkey-rp-id
Binds passkeys to another RP ID – one of the instance’s domains or a parent domain of one (needs instance.manage). Passkeys registered under the former RP ID stop working.
PUT
/v1/instance/primary-domain
Sets the domain the issuer and links use (needs instance.manage). Passkeys are bound to it.
PUT
/v1/instance/registration-tenant
Names the tenant where people without an address of a tenant register (needs instance.manage).
GET
/v1/instance/roles
The instance roles and who holds them (needs instance.read).
PUT
/v1/instance/roles/{role}
Defines an instance role of the instance’s own, or changes its permissions (needs instance_roles.manage).
DELETE
/v1/instance/roles/{role}
Deletes an instance role of the instance’s own, once nobody holds it (needs instance_roles.manage).
PUT
/v1/instance/roles/{role}/{kind}/{id}
Assigns an instance role to an account or group of any tenant of the instance (needs instance_roles.manage).
DELETE
/v1/instance/roles/{role}/{kind}/{id}
Takes an instance role back; the last instance_admin stays (needs instance_roles.manage).
GET
/v1/instance/signing-keys
The instance’s signing keys (needs instance.read).
POST
/v1/instance/signing-keys
Creates a signing key: published at once, signing once activated (needs instance.manage).
DELETE
/v1/instance/signing-keys/{kid}
Removes an inactive key whose tokens have expired (needs instance.manage).
POST
/v1/instance/signing-keys/{kid}/activate
Signs with this key from now on (needs instance.manage).
GET
/v1/instance/tenants
The instance’s tenants (needs instance.read).
POST
/v1/instance/tenants
Creates a tenant (needs tenants.manage).
PATCH
/v1/instance/tenants/{id}
Renames, deactivates or reactivates a tenant (needs tenants.manage); not one’s own by deactivation.
Health
Method
Path
What it does
GET
/healthz
Liveness: the process runs. Says nothing about its dependencies, so an orchestrator does not restart every process when the database is away.
GET
/readyz
Readiness: PostgreSQL and object storage answer. Details of failures go to the log, not to the caller.
Meta
Method
Path
What it does
GET
/version
The name and version of this kernoid.
OpenID Connect
The protocol endpoints (/.well-known/openid-configuration, /oauth/v2/…, /oidc/v1/…) are standard OpenID Connect and OAuth 2.0 and not part of the OpenAPI description – see Login and OpenID Connect. The same holds for SCIM 2.0 at /scim/v2 and the identity provider endpoints /idp/callback, /idp/saml/acs and /idp/saml/metadata – see Identity providers and SCIM.