Preview — not for production reliance
Documentation

Docs API reference

Auth

Sign-in, sessions and passkeys.

10 operations. See the API overview for authentication, errors, idempotency and caching rules that apply to all of them.

Sign-in methods this deployment offers

GET/auth/providers

Authentication
Public, no credentials
Operation ID
getAuthProviders

For the console sign-in page. Never reveals configuration values. test_login is true only where the test identity provider is mounted and enabled (never in production); on staging test_login_key_required is also true.

Responses

StatusDescriptionBody
200Available sign-in methods.
Headers: Cache-Control: no-store
application/json AuthProviders
404not_found. No such resource, or one the caller may not see (the two are indistinguishable by design).application/json Error

200 example

json
{
  "github": true,
  "google": true,
  "test_login": false,
  "test_login_key_required": false
}

404 example

json
{
  "code": "not_found",
  "message": "No such resource.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

Start an OIDC sign-in

GET/auth/login/{provider}

Authentication
Public, no credentials
Operation ID
startLogin

Redirects the browser to the identity provider (authorization code with PKCE and a nonce). Sets the single-use, HttpOnly browser-binding cookie __Host-art_oidc. Rate limited per client.

Parameters

NameInRequiredTypeDescription
providerpathYesone of "github", "google"
return_toqueryNostringA same-site absolute path to return to after sign-in (at most 512 printable ASCII characters, starting with a single /). Anything else is replaced by the default.
at most 512 characters
example /console/

Responses

StatusDescriptionBody
302Redirect to the identity provider.
Headers: Location, Set-Cookie
None
404not_found. No such resource, or one the caller may not see (the two are indistinguishable by design).application/json Error
429rate_limited. A per-client or per-record limit was reached; retry later.application/json Error
503unavailable (temporarily unavailable; also a feature that is not configured in this deployment) or provider_unavailable (an identity provider is not configured). Both are 503. During maintenance (for example a database recovery) every mutating request answers unavailable "The directory is under maintenance; try again later." Reads keep working.application/json Error

404 example

json
{
  "code": "not_found",
  "message": "No such resource.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

429 example

json
{
  "code": "rate_limited",
  "message": "Too many requests.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

503 example

json
{
  "code": "unavailable",
  "message": "Temporarily unavailable.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

OIDC redirect target

GET/auth/callback/{provider}

Authentication
Public, no credentials
Operation ID
completeLogin

Where the identity provider returns the browser. Exchanges the code, upserts the principal, revokes any older session the browser held and sets the session cookie __Host-art_session (HttpOnly, Secure, SameSite=Lax, 12 hours), then redirects to the original return_to. The browser-binding cookie is cleared on every outcome. A session created here has no MFA yet: complete a passkey ceremony before using the API. Not for direct use by API clients.

Parameters

NameInRequiredTypeDescription
providerpathYesone of "github", "google"
codequeryNostring
statequeryNostring
errorqueryNostringSet by the provider when the user cancelled.

Responses

StatusDescriptionBody
302Signed in; redirect to the return path.
Headers: Location, Set-Cookie
None
400invalid_request. Malformed JSON, an invalid or unknown field (see field_errors), a missing or malformed Idempotency-Key, or a bad query. Also used with status 413 (body over 64 KiB) and 415 (wrong Content-Type).application/json Error
403forbidden. Authenticated but not allowed: a role that does not permit the action, a service token on a human-only operation, a missing or wrong CSRF token, or a request Origin that is not allowed.application/json Error
404not_found. No such resource, or one the caller may not see (the two are indistinguishable by design).application/json Error
429rate_limited. A per-client or per-record limit was reached; retry later.application/json Error
503unavailable (temporarily unavailable; also a feature that is not configured in this deployment) or provider_unavailable (an identity provider is not configured). Both are 503. During maintenance (for example a database recovery) every mutating request answers unavailable "The directory is under maintenance; try again later." Reads keep working.application/json Error

400 example

json
{
  "code": "invalid_request",
  "message": "Invalid organization.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w",
  "field_errors": [
    {
      "field": "legal_name",
      "message": "must be 1-200 characters"
    }
  ]
}

403 example

json
{
  "code": "forbidden",
  "message": "Your role does not permit this action.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

404 example

json
{
  "code": "not_found",
  "message": "No such resource.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

429 example

json
{
  "code": "rate_limited",
  "message": "Too many requests.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

503 example

json
{
  "code": "unavailable",
  "message": "Temporarily unavailable.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

Test identity provider (non-production)

POST/auth/test-login

Authentication
Public, no credentials
Operation ID
testLogin

Exists only in dev, test and staging deployments with the test IdP enabled; it is not mounted in production. Creates a session for a synthetic principal, without MFA. On staging the header x-art-test-idp-key must equal the deployment's tester key; a wrong or missing key (like a deployment without the route) answers 404, not 401. Sets the session cookie.

Parameters

NameInRequiredTypeDescription
x-art-test-idp-keyheaderNostringTester key (staging only).

Request body

application/json, required: TestLoginRequest

FieldTypeRequiredDescription
subjectstringYespattern ^[A-Za-z0-9._@-]{1,128}$
emailstring (email)Yesat most 254 characters

Example

json
{
  "subject": "tester-1",
  "email": "tester-1@example.com"
}

Responses

StatusDescriptionBody
201Session created.
Headers: Set-Cookie, Cache-Control: no-store
application/json TestLoginResponse
400invalid_request. Malformed JSON, an invalid or unknown field (see field_errors), a missing or malformed Idempotency-Key, or a bad query. Also used with status 413 (body over 64 KiB) and 415 (wrong Content-Type).application/json Error
403forbidden. Authenticated but not allowed: a role that does not permit the action, a service token on a human-only operation, a missing or wrong CSRF token, or a request Origin that is not allowed.application/json Error
404not_found. No such resource, or one the caller may not see (the two are indistinguishable by design).application/json Error
429rate_limited. A per-client or per-record limit was reached; retry later.application/json Error

201 example

json
{
  "principal_id": "prn_6d9g2j5m8q1t4w7z0c3f6h9k2n",
  "has_passkey": false,
  "mfa": false
}

400 example

json
{
  "code": "invalid_request",
  "message": "Invalid organization.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w",
  "field_errors": [
    {
      "field": "legal_name",
      "message": "must be 1-200 characters"
    }
  ]
}

403 example

json
{
  "code": "forbidden",
  "message": "Your role does not permit this action.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

404 example

json
{
  "code": "not_found",
  "message": "No such resource.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

429 example

json
{
  "code": "rate_limited",
  "message": "Too many requests.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

Current session

GET/auth/session

Authentication
Session, before the passkey step
Operation ID
getSession

The signed-in principal, whether the session has completed a passkey assertion (mfa), the CSRF token to send as x-art-csrf on mutations, and the principal's roles. Works for a session that has not completed MFA yet.

Responses

StatusDescriptionBody
200Session details.
Headers: Cache-Control: no-store
application/json SessionInfo
401unauthenticated (no or invalid credentials, or the session has not completed the passkey ceremony) or step_up_required (the passkey assertion is too old; also returned to service tokens, which can never step up). Both are 401; tell them apart by code.application/json Error
503unavailable (temporarily unavailable; also a feature that is not configured in this deployment) or provider_unavailable (an identity provider is not configured). Both are 503. During maintenance (for example a database recovery) every mutating request answers unavailable "The directory is under maintenance; try again later." Reads keep working.application/json Error

200 example

json
{
  "principal": {
    "id": "prn_6d9g2j5m8q1t4w7z0c3f6h9k2n",
    "idp": "github",
    "email": "ada@example.com",
    "display_name": "Ada Lovelace"
  },
  "mfa": true,
  "mfa_at": "2026-10-05T11:55:00.000Z",
  "has_passkey": true,
  "csrf_token": "Kk3jVq0bYw2p0mT1aZc9sD8eF4gH6iJ7nL5oP2rQ1uA",
  "roles": {
    "platform": [],
    "organizations": [
      {
        "organization_id": "org_7k3m9q2x8d5f1h4j6n0p2r4t8v",
        "role": "organization_owner"
      }
    ]
  },
  "expires_at": "2026-10-05T23:50:00.000Z"
}

401 unauthenticated

json
{
  "code": "unauthenticated",
  "message": "Sign in required.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

401 step_up_required

json
{
  "code": "step_up_required",
  "message": "Recent passkey verification required for this action.",
  "request_id": "req_5a8c1f4h7k0m3p6s9v2x5z8b1d"
}

503 example

json
{
  "code": "unavailable",
  "message": "Temporarily unavailable.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

Sign out

POST/auth/logout

Authentication
Session, before the passkey step
Operation ID
logout

Revokes the current session and clears the cookie.

Parameters

NameInRequiredTypeDescription
x-art-csrfheaderYesstringThe csrf_token from GET /auth/session. Required on every session-authenticated mutation.
example Kk3jVq0bYw2p0mT1aZc9sD8eF4gH6iJ7nL5oP2rQ1uA

Responses

StatusDescriptionBody
204Signed out.
Headers: Set-Cookie
None
401unauthenticated (no or invalid credentials, or the session has not completed the passkey ceremony) or step_up_required (the passkey assertion is too old; also returned to service tokens, which can never step up). Both are 401; tell them apart by code.application/json Error
403forbidden. Authenticated but not allowed: a role that does not permit the action, a service token on a human-only operation, a missing or wrong CSRF token, or a request Origin that is not allowed.application/json Error
503unavailable (temporarily unavailable; also a feature that is not configured in this deployment) or provider_unavailable (an identity provider is not configured). Both are 503. During maintenance (for example a database recovery) every mutating request answers unavailable "The directory is under maintenance; try again later." Reads keep working.application/json Error

401 unauthenticated

json
{
  "code": "unauthenticated",
  "message": "Sign in required.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

401 step_up_required

json
{
  "code": "step_up_required",
  "message": "Recent passkey verification required for this action.",
  "request_id": "req_5a8c1f4h7k0m3p6s9v2x5z8b1d"
}

403 example

json
{
  "code": "forbidden",
  "message": "Your role does not permit this action.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

503 example

json
{
  "code": "unavailable",
  "message": "Temporarily unavailable.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

Begin passkey registration

POST/auth/passkeys/registration/options

Authentication
Session, before the passkey step or Session with step-up (passkey within 300 s)
Operation ID
getPasskeyRegistrationOptions

WebAuthn registration options (PublicKeyCredentialCreationOptionsJSON). Registering the first passkey needs only a signed-in session. Registering an additional passkey needs a passkey-verified session and a fresh step-up. Rate limited.

Parameters

NameInRequiredTypeDescription
x-art-csrfheaderYesstringThe csrf_token from GET /auth/session. Required on every session-authenticated mutation.
example Kk3jVq0bYw2p0mT1aZc9sD8eF4gH6iJ7nL5oP2rQ1uA

Responses

StatusDescriptionBody
200Registration options for navigator.credentials.create().
Headers: Cache-Control: no-store
application/json WebAuthnOptions
401unauthenticated (no or invalid credentials, or the session has not completed the passkey ceremony) or step_up_required (the passkey assertion is too old; also returned to service tokens, which can never step up). Both are 401; tell them apart by code.application/json Error
403forbidden. Authenticated but not allowed: a role that does not permit the action, a service token on a human-only operation, a missing or wrong CSRF token, or a request Origin that is not allowed.application/json Error
429rate_limited. A per-client or per-record limit was reached; retry later.application/json Error
503unavailable (temporarily unavailable; also a feature that is not configured in this deployment) or provider_unavailable (an identity provider is not configured). Both are 503. During maintenance (for example a database recovery) every mutating request answers unavailable "The directory is under maintenance; try again later." Reads keep working.application/json Error

200 example

json
{
  "rp": {
    "name": "Agentic Root Trust",
    "id": "svid.io"
  },
  "user": {
    "id": "cHJuXzZkOWcyajVtOHExdDR3N3owYzNmNmg5azJu",
    "name": "ada@example.com",
    "displayName": "Ada Lovelace"
  },
  "challenge": "m3Qx0Zk8Vb1Nn7Yp2Tt6Ra4Ew9Hs5Dc",
  "pubKeyCredParams": [
    {
      "type": "public-key",
      "alg": -7
    }
  ],
  "timeout": 60000,
  "attestation": "none"
}

401 unauthenticated

json
{
  "code": "unauthenticated",
  "message": "Sign in required.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

401 step_up_required

json
{
  "code": "step_up_required",
  "message": "Recent passkey verification required for this action.",
  "request_id": "req_5a8c1f4h7k0m3p6s9v2x5z8b1d"
}

403 example

json
{
  "code": "forbidden",
  "message": "Your role does not permit this action.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

429 example

json
{
  "code": "rate_limited",
  "message": "Too many requests.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

503 example

json
{
  "code": "unavailable",
  "message": "Temporarily unavailable.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

Finish passkey registration

POST/auth/passkeys/registration/verify

Authentication
Session, before the passkey step or Session with step-up (passkey within 300 s)
Operation ID
verifyPasskeyRegistration

Verifies the attestation, stores the credential and marks the session as passkey-verified (MFA).

Parameters

NameInRequiredTypeDescription
x-art-csrfheaderYesstringThe csrf_token from GET /auth/session. Required on every session-authenticated mutation.
example Kk3jVq0bYw2p0mT1aZc9sD8eF4gH6iJ7nL5oP2rQ1uA

Request body

application/json, required: PasskeyCeremonyRequest

FieldTypeRequiredDescription
responseobjectYesThe RegistrationResponseJSON or AuthenticationResponseJSON from the browser.

Example

json
{
  "response": {
    "id": "AQIDBAUGBwgJ",
    "rawId": "AQIDBAUGBwgJ",
    "type": "public-key",
    "response": {
      "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0",
      "attestationObject": "o2NmbXRkbm9uZQ"
    },
    "clientExtensionResults": {}
  }
}

Responses

StatusDescriptionBody
201Credential registered; the session now has MFA.application/json PasskeyRegistered
400invalid_request. Malformed JSON, an invalid or unknown field (see field_errors), a missing or malformed Idempotency-Key, or a bad query. Also used with status 413 (body over 64 KiB) and 415 (wrong Content-Type).application/json Error
401unauthenticated (no or invalid credentials, or the session has not completed the passkey ceremony) or step_up_required (the passkey assertion is too old; also returned to service tokens, which can never step up). Both are 401; tell them apart by code.application/json Error
403forbidden. Authenticated but not allowed: a role that does not permit the action, a service token on a human-only operation, a missing or wrong CSRF token, or a request Origin that is not allowed.application/json Error
429rate_limited. A per-client or per-record limit was reached; retry later.application/json Error
503unavailable (temporarily unavailable; also a feature that is not configured in this deployment) or provider_unavailable (an identity provider is not configured). Both are 503. During maintenance (for example a database recovery) every mutating request answers unavailable "The directory is under maintenance; try again later." Reads keep working.application/json Error

201 example

json
{
  "credential_id": "AQIDBAUGBwgJ",
  "mfa": true,
  "mfa_at": "2026-10-05T11:55:00.000Z"
}

400 example

json
{
  "code": "invalid_request",
  "message": "Invalid organization.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w",
  "field_errors": [
    {
      "field": "legal_name",
      "message": "must be 1-200 characters"
    }
  ]
}

401 unauthenticated

json
{
  "code": "unauthenticated",
  "message": "Sign in required.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

401 step_up_required

json
{
  "code": "step_up_required",
  "message": "Recent passkey verification required for this action.",
  "request_id": "req_5a8c1f4h7k0m3p6s9v2x5z8b1d"
}

403 example

json
{
  "code": "forbidden",
  "message": "Your role does not permit this action.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

429 example

json
{
  "code": "rate_limited",
  "message": "Too many requests.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

503 example

json
{
  "code": "unavailable",
  "message": "Temporarily unavailable.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

Begin passkey assertion (MFA or step-up)

POST/auth/passkeys/authentication/options

Authentication
Session, before the passkey step
Operation ID
getPasskeyAuthenticationOptions

WebAuthn assertion options (PublicKeyCredentialRequestOptionsJSON). Use after sign-in to complete MFA, and again to refresh the step-up window before a sensitive action.

Parameters

NameInRequiredTypeDescription
x-art-csrfheaderYesstringThe csrf_token from GET /auth/session. Required on every session-authenticated mutation.
example Kk3jVq0bYw2p0mT1aZc9sD8eF4gH6iJ7nL5oP2rQ1uA

Responses

StatusDescriptionBody
200Assertion options for navigator.credentials.get().
Headers: Cache-Control: no-store
application/json WebAuthnOptions
401unauthenticated (no or invalid credentials, or the session has not completed the passkey ceremony) or step_up_required (the passkey assertion is too old; also returned to service tokens, which can never step up). Both are 401; tell them apart by code.application/json Error
403forbidden. Authenticated but not allowed: a role that does not permit the action, a service token on a human-only operation, a missing or wrong CSRF token, or a request Origin that is not allowed.application/json Error
429rate_limited. A per-client or per-record limit was reached; retry later.application/json Error
503unavailable (temporarily unavailable; also a feature that is not configured in this deployment) or provider_unavailable (an identity provider is not configured). Both are 503. During maintenance (for example a database recovery) every mutating request answers unavailable "The directory is under maintenance; try again later." Reads keep working.application/json Error

200 example

json
{
  "challenge": "Pq7Lw2Hn5Bv8Xc1Zd4Ke6Ry9Tj3Um0Fs",
  "rpId": "svid.io",
  "allowCredentials": [
    {
      "type": "public-key",
      "id": "AQIDBAUGBwgJ"
    }
  ],
  "userVerification": "required",
  "timeout": 60000
}

401 unauthenticated

json
{
  "code": "unauthenticated",
  "message": "Sign in required.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

401 step_up_required

json
{
  "code": "step_up_required",
  "message": "Recent passkey verification required for this action.",
  "request_id": "req_5a8c1f4h7k0m3p6s9v2x5z8b1d"
}

403 example

json
{
  "code": "forbidden",
  "message": "Your role does not permit this action.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

429 example

json
{
  "code": "rate_limited",
  "message": "Too many requests.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

503 example

json
{
  "code": "unavailable",
  "message": "Temporarily unavailable.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

Finish passkey assertion

POST/auth/passkeys/authentication/verify

Authentication
Session, before the passkey step
Operation ID
verifyPasskeyAuthentication

Verifies the assertion and sets mfa_at on the session. Step-up is satisfied for the next 300 seconds.

Parameters

NameInRequiredTypeDescription
x-art-csrfheaderYesstringThe csrf_token from GET /auth/session. Required on every session-authenticated mutation.
example Kk3jVq0bYw2p0mT1aZc9sD8eF4gH6iJ7nL5oP2rQ1uA

Request body

application/json, required: PasskeyCeremonyRequest

FieldTypeRequiredDescription
responseobjectYesThe RegistrationResponseJSON or AuthenticationResponseJSON from the browser.

Example

json
{
  "response": {
    "id": "AQIDBAUGBwgJ",
    "rawId": "AQIDBAUGBwgJ",
    "type": "public-key",
    "response": {
      "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0In0",
      "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2M",
      "signature": "MEUCIQDexample",
      "userHandle": "cHJuXzZkOWcyajVtOHExdDR3N3owYzNmNmg5azJu"
    },
    "clientExtensionResults": {}
  }
}

Responses

StatusDescriptionBody
200Assertion verified.application/json PasskeyAuthenticated
400invalid_request. Malformed JSON, an invalid or unknown field (see field_errors), a missing or malformed Idempotency-Key, or a bad query. Also used with status 413 (body over 64 KiB) and 415 (wrong Content-Type).application/json Error
401unauthenticated (no or invalid credentials, or the session has not completed the passkey ceremony) or step_up_required (the passkey assertion is too old; also returned to service tokens, which can never step up). Both are 401; tell them apart by code.application/json Error
403forbidden. Authenticated but not allowed: a role that does not permit the action, a service token on a human-only operation, a missing or wrong CSRF token, or a request Origin that is not allowed.application/json Error
429rate_limited. A per-client or per-record limit was reached; retry later.application/json Error
503unavailable (temporarily unavailable; also a feature that is not configured in this deployment) or provider_unavailable (an identity provider is not configured). Both are 503. During maintenance (for example a database recovery) every mutating request answers unavailable "The directory is under maintenance; try again later." Reads keep working.application/json Error

200 example

json
{
  "mfa": true,
  "mfa_at": "2026-10-05T12:00:00.000Z"
}

400 example

json
{
  "code": "invalid_request",
  "message": "Invalid organization.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w",
  "field_errors": [
    {
      "field": "legal_name",
      "message": "must be 1-200 characters"
    }
  ]
}

401 unauthenticated

json
{
  "code": "unauthenticated",
  "message": "Sign in required.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

401 step_up_required

json
{
  "code": "step_up_required",
  "message": "Recent passkey verification required for this action.",
  "request_id": "req_5a8c1f4h7k0m3p6s9v2x5z8b1d"
}

403 example

json
{
  "code": "forbidden",
  "message": "Your role does not permit this action.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

429 example

json
{
  "code": "rate_limited",
  "message": "Too many requests.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

503 example

json
{
  "code": "unavailable",
  "message": "Temporarily unavailable.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}