openapi: 3.1.0
info:
  title: Lucion Identity API
  version: 6.6.6
  description: >-
    Production API contract for Lucion Identity. Browser sessions support
    passkeys and password accounts; passkeys remain available as a
    phishing-resistant sign-in method but are not required for account creation
    or password sign-in.
    Password proofs use a server-only pepper, per-credential salt, and a
    Cloudflare-safe chained PBKDF2-SHA-256 work factor.
    exact-origin and CSRF protections, OAuth/OIDC, scoped API keys, organization
    isolation, SCIM, signed outbound/inbound webhooks, hash-only idempotency
    records, durable bounded webhook delivery, tenant-scoped lifecycle routing,
    and session revocation lifecycle events that exclude session credentials. Federation
    activation validates bounded provider metadata before state changes, while SAML
    assertion execution remains independently gated. Scoped CLI mutations require
    narrow API-key scopes and explicit idempotency keys. Maintained TypeScript,
    Python, and Go SDKs cover the tenant organization mutation and inspection
    surfaces plus scoped developer metadata reads with caller-supplied idempotency
    keys where mutations are supported. Developer reads require identity.developer.read
    and return only owner-filtered metadata with bounded opaque cursors.
    Versioned API surfaces advertise their lifecycle and reject unsupported
    requested versions before authentication or data access.
  x-logo:
    url: /assets/identity-brand/lucion-identity-horizontal.png
    altText: Lucion Identity
servers:
  - url: https://identity.lucionglobal.com
    description: Canonical production origin
externalDocs:
  description: Human-readable API and webhook reference
  url: https://identity.lucionglobal.com/docs
x-lucion-api-version: v1
x-lucion-api-lifecycle: current
x-lucion-status-evidence:
  capabilities: /api/v1/capabilities
  readiness: /api/v1/readiness
  rehearsal: /api/v1/rehearsal
  status_page: /status
  fallback: unavailable
x-lucion-scim-concurrency:
  etag: strong-opaque-resource-version
  meta_version: true
  if_none_match: 304
  if_match: 412
  wildcard_if_match: supported
x-lucion-versioning-policy:
  current: v1
  supported: [v1]
  deprecation_notice_days: 180
  request_header: X-Lucion-API-Version
  response_header: X-Lucion-API-Version
  lifecycle_header: X-Lucion-API-Lifecycle
  sunset_header: Sunset
  documentation: /docs/versioning
  scim_current: scim-2.0
paths:
  /api/health:
    get:
      summary: Read safe release and runtime status
      responses: { '200': { $ref: '#/components/responses/Json200' } }
  /api/docs/ask:
    post:
      summary: Ask the bounded documentation assistant
      description: Requires the exact site Origin. Accepts a bounded documentation question and page context, rate-limits by a keyed client identifier, and returns advisory text only. Provider API keys and response bodies remain server-side; Lucion AI is preferred when configured and Gemini 3 Flash is the fallback.
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '403': { $ref: '#/components/responses/Json403' }, '429': { $ref: '#/components/responses/Json429' }, '502': { $ref: '#/components/responses/Json502' }, '503': { $ref: '#/components/responses/Json503' }, '504': { $ref: '#/components/responses/Json503' } }
  /api/auth/status:
    get:
      summary: Read authentication capability status
      responses: { '200': { $ref: '#/components/responses/Json200' } }
  /api/auth/passkeys/capabilities:
    get:
      summary: Read passkey and WebAuthn capability contract
      responses: { '200': { $ref: '#/components/responses/Json200' } }
  /api/auth/password/capabilities:
    get:
      summary: Read password authentication capability contract
      description: Returns safe password authentication metadata. Password accounts require verified email enrollment; password proofs are independently gated, rate-limited, and lockout-protected.
      responses: { '200': { $ref: '#/components/responses/Json200' } }
  /api/auth/verification/capabilities:
    get:
      summary: Read six-digit verification UX contract
      responses: { '200': { $ref: '#/components/responses/Json200' } }
  /api/auth/interaction/capabilities:
    get:
      summary: Read Trust UX capability contract
      responses: { '200': { $ref: '#/components/responses/Json200' } }
  /api/auth/enrollment/capabilities:
    get:
      summary: Read email and passkey enrollment readiness
      responses: { '200': { $ref: '#/components/responses/Json200' } }
  /api/auth/email/capabilities:
    get:
      summary: Read transactional email and Resend webhook status
      responses: { '200': { $ref: '#/components/responses/Json200' } }
  /api/auth/session/capabilities:
    get:
      summary: Read session security capability status
      responses: { '200': { $ref: '#/components/responses/Json200' } }
  /api/auth/recovery/capabilities:
    get:
      summary: Read recovery capability status
      responses: { '200': { $ref: '#/components/responses/Json200' } }
  /api/auth/authorization/capabilities:
    get:
      summary: Read organization and Lyra authorization status
      responses: { '200': { $ref: '#/components/responses/Json200' } }
  /api/auth/federation/capabilities:
    get:
      summary: Read the enterprise federation capability contract
      description: Returns safe provider-level status. Inbound OIDC may be enabled only behind the independent federation gate; SAML protocol execution remains independently gated until conformance review. SAML metadata validation is required before a connection can be activated. No provider credentials, tenant identifiers, or assertion data are returned.
      responses: { '200': { $ref: '#/components/responses/Json200' } }
  /api/auth/federation/discovery:
    get:
      summary: Discover an enabled federation connection for a verified email domain
      description: Requires the exact production Origin. The endpoint never returns an organization identifier or provider credentials; protocol execution remains independently gated.
      parameters:
        - name: email
          in: query
          required: true
          schema: { type: string, format: email, maxLength: 320 }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '403': { $ref: '#/components/responses/Json403' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/v1/readiness:
    get:
      summary: Read the production readiness matrix
      responses: { '200': { $ref: '#/components/responses/Json200' } }
  /api/v1/capabilities:
    get:
      summary: Read the authoritative public capability matrix
      description: Returns safe live, gated, degraded, manual, and planned capability classifications derived from runtime readiness and rehearsal boundaries. This endpoint never grants access and never returns secrets, provider bodies, tenant identifiers, session material, or verification data.
      responses: { '200': { $ref: '#/components/responses/Json200' } }
  /api/v1/rehearsal:
    get:
      summary: Read the fail-closed launch rehearsal state
      responses: { '200': { $ref: '#/components/responses/Json200' } }
  /api/openapi.json:
    get:
      summary: Fetch the machine-readable API contract
      responses: { '200': { description: OpenAPI 3.1 JSON document } }
  /api/v1/me:
    get:
      summary: Read the authenticated identity profile
      security: [{ BearerAuth: [identity.profile.read] }, { BearerAuth: [identity.email.read] }]
      responses:
        '200': { $ref: '#/components/responses/Json200' }
        '401': { $ref: '#/components/responses/Json401' }
        '403': { $ref: '#/components/responses/Json403' }
    patch:
      summary: Update the authenticated identity profile
      description: Requires identity.profile.write and Idempotency-Key. Updates display_name, locale, and timezone only; email ownership changes require a separate verification flow. Successful changes emit privacy-minimized identity.user.updated events through the durable outbox.
      security: [{ BearerAuth: [identity.profile.write] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/v1/organizations:
    get:
      summary: List the bearer token's organization
      description: Server-to-server read requiring an organization-bound bearer credential with identity.organization.read. The response is limited to that credential's tenant.
      security: [{ BearerAuth: [identity.organization.read] }]
      parameters: [{ $ref: '#/components/parameters/PageLimit' }, { $ref: '#/components/parameters/PageCursor' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/v1/organizations/{id}:
    get:
      summary: Read an organization
      description: Server-to-server read. The requested organization must exactly match the bearer credential's organization binding. Administrative mutations remain on the session, CSRF, and step-up protected browser contract.
      security: [{ BearerAuth: [identity.organization.read] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '404': { $ref: '#/components/responses/Json404' } }
  /api/v1/me/memberships:
    get:
      summary: Read the authenticated Lucion ID's own organization memberships
      description: Returns active memberships for the bearer credential owner only. No organization selector is accepted, so this route cannot enumerate another user's or tenant's memberships.
      security: [{ BearerAuth: [identity.organization.read] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/v1/organizations/{id}/members:
    get:
      summary: List organization members
      description: Returns a tenant-bound, stable descending created_at/id page. Pass next_cursor unchanged for the next page; limit is bounded from 1 through 100.
      security: [{ BearerAuth: [identity.organization.read] }]
      parameters: [{ $ref: '#/components/parameters/PageLimit' }, { $ref: '#/components/parameters/PageCursor' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' } }
  /api/v1/organizations/{id}/invitations:
    get:
      summary: List organization invitations
      description: Returns a tenant-bound, stable descending created_at/id page. Pass next_cursor unchanged for the next page; limit is bounded from 1 through 100.
      security: [{ BearerAuth: [identity.organization.read] }]
      parameters: [{ $ref: '#/components/parameters/PageLimit' }, { $ref: '#/components/parameters/PageCursor' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' } }
  /api/v1/organizations/{id}/groups:
    get:
      summary: List organization groups
      description: Returns a tenant-bound, stable descending created_at/id page. Pass next_cursor unchanged for the next page; limit is bounded from 1 through 100.
      security: [{ BearerAuth: [identity.organization.read] }]
      parameters: [{ $ref: '#/components/parameters/PageLimit' }, { $ref: '#/components/parameters/PageCursor' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' } }
  /api/v1/organizations/{id}/groups/{group_id}/members:
    get:
      summary: List members of an organization group
      description: Returns a tenant-bound, stable descending membership created_at/id page. Pass next_cursor unchanged for the next page; limit is bounded from 1 through 100.
      security: [{ BearerAuth: [identity.organization.members.read] }]
      parameters: [{ $ref: '#/components/parameters/PageLimit' }, { $ref: '#/components/parameters/PageCursor' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '404': { $ref: '#/components/responses/Json404' } }
  /api/v1/organizations/{id}/domains:
    get:
      summary: List verified organization domains
      description: Returns a tenant-bound, stable descending created_at/id page. Pass next_cursor unchanged for the next page; limit is bounded from 1 through 100.
      security: [{ BearerAuth: [identity.organization.read] }]
      parameters: [{ $ref: '#/components/parameters/PageLimit' }, { $ref: '#/components/parameters/PageCursor' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' } }
  /api/v1/organizations/{id}/lyra/actions:
    get:
      summary: List the organization Lyra action catalog
      description: Returns a tenant-bound, stable descending created_at/id page. Pass next_cursor unchanged for the next page; limit is bounded from 1 through 100. Action execution remains separately gated and disabled by default.
      security: [{ BearerAuth: [identity.organization.read] }]
      parameters: [{ $ref: '#/components/parameters/PageLimit' }, { $ref: '#/components/parameters/PageCursor' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' } }
  /api/v1/privacy/export:
    get:
      summary: Export the authenticated identity data boundary
      description: Requires the dedicated identity.privacy.export scope. Returns the authenticated user's profile, memberships, consents, delegations, session metadata, authenticator metadata, and privacy-minimized activity. Hashes, tokens, cookies, verification codes, private keys, webhook secrets, provider bodies, and IP/user-agent material are never returned. The response is never cached.
      security: [{ BearerAuth: [identity.privacy.export] }]
      responses:
        '200': { $ref: '#/components/responses/Json200' }
        '401': { $ref: '#/components/responses/Json401' }
        '403': { $ref: '#/components/responses/Json403' }
        '503': { $ref: '#/components/responses/Json503' }
  /api/auth/federation/oidc/start:
    get:
      summary: Start an inbound OIDC authorization-code transaction
      description: Requires the independently enabled federation gate and exact production Origin. The server fetches bounded HTTPS provider metadata and creates an expiring state/nonce/PKCE transaction without exposing provider credentials.
      parameters:
        - name: connection_id
          in: query
          required: true
          schema: { type: string }
      responses:
        '302': { description: Redirect to the validated provider authorization endpoint }
        '400': { $ref: '#/components/responses/Json400' }
        '403': { $ref: '#/components/responses/Json403' }
        '503': { $ref: '#/components/responses/Json503' }
  /api/auth/federation/oidc/callback:
    get:
      summary: Complete an inbound OIDC transaction
      description: The canonical-origin callback exchanges with PKCE and validates a signed RS256 ID token for issuer, audience, nonce, time, and verified email before tenant-bound JIT membership and a federated session are created. Transactions are single-use.
      parameters:
        - name: state
          in: query
          required: true
          schema: { type: string }
        - name: code
          in: query
          required: true
          schema: { type: string }
      responses:
        '302': { description: Redirect to the sign-in surface with a safe result classification }
  /api/auth/privacy/deletion-request:
    get:
      summary: Read the current account deletion-request state
      description: Returns only the authenticated account's latest deletion-request metadata. No deletion is started by this read.
      security: [{ SessionCookie: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' }, '503': { $ref: '#/components/responses/Json503' } }
    post:
      summary: Request account deletion for manual privacy review
      description: Requires exact Origin, CSRF, recent passkey step-up, explicit confirmation DELETE_MY_ACCOUNT, and Idempotency-Key. Creates a pending user-bound request only; it never immediately deletes identity data or credentials.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '202': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '409': { $ref: '#/components/responses/Json409' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/auth/privacy/deletion-request/cancel:
    post:
      summary: Cancel a pending account deletion request
      description: Requires exact Origin, CSRF, recent passkey step-up, explicit confirmation CANCEL_DELETION, and Idempotency-Key. Only a pending request may be cancelled.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '404': { $ref: '#/components/responses/Json404' }, '409': { $ref: '#/components/responses/Json409' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/auth/session:
    get:
      summary: Resolve the current secure session
      responses: { '200': { $ref: '#/components/responses/Json200' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/auth/oauth/connections:
    get:
      summary: List the current user's active OAuth and assistant connections
      description: Returns names, granted scopes, connection type, and expiry only. Raw OAuth tokens are never exposed.
      security: [{ SessionCookie: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/auth/oauth/connections/{connection_id}:
    delete:
      summary: Revoke one of the current user's OAuth or assistant connections
      description: Requires exact Origin, CSRF, and an authenticated session. Revokes the refresh-token family and every linked access token. The operation is safe to repeat.
      security: [{ SessionCookie: [] }]
      parameters: [{ name: connection_id, in: path, required: true, schema: { type: string, pattern: '^orf_[0-9a-fA-F-]{36}$' } }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '404': { $ref: '#/components/responses/Json404' } }
  /api/auth/sessions:
    get:
      summary: List active sessions for the current account
      security: [{ SessionCookie: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' } }
  /api/auth/sessions/revoke:
    post:
      summary: Revoke one owned session
      description: Requires exact Origin, CSRF, and an authenticated session. Emits identity.session.revoked through the durable outbox after the guarded update succeeds.
      security: [{ SessionCookie: [] }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '404': { $ref: '#/components/responses/Json404' } }
  /api/auth/signout:
    post:
      summary: Revoke the current session
      description: Requires exact Origin and CSRF when authenticated. Emits identity.session.revoked through the durable outbox after the guarded update succeeds.
      security: [{ SessionCookie: [] }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' } }
  /api/auth/enrollment/start:
    post:
      summary: Start verified-email enrollment
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '202': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '429': { $ref: '#/components/responses/Json429' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/auth/enrollment/verify-email:
    post:
      summary: Verify the six-digit email code
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' } }
  /api/auth/enrollment/password/complete:
    post:
      summary: Create an account with a verified email and password
      description: Requires a verified email enrollment token and a valid password. Creates the account and stores only the server-side password proof; it never issues a session.
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '409': { $ref: '#/components/responses/Json409' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/auth/enrollment/resend-email:
    post:
      summary: Request a replacement verification email
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '429': { $ref: '#/components/responses/Json429' } }
  /api/auth/enrollment/passkey/options:
    post:
      summary: Issue one-time WebAuthn registration options
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' } }
  /api/auth/enrollment/passkey/complete:
    post:
      summary: Verify and persist a passkey
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/auth/signin/passkey/options:
    post:
      summary: Issue a one-time passkey sign-in challenge
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '429': { $ref: '#/components/responses/Json429' } }
  /api/auth/signin/passkey/complete:
    post:
      summary: Verify a passkey assertion and issue a secure session cookie
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/auth/signin/password:
    post:
      summary: Verify a password and issue a secure session cookie
      description: Requires the exact production Origin. Password proofs use a server-only pepper and per-credential salt, generic failure responses, bounded rate limits, and lockout after repeated failures. A passkey is not required.
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '429': { $ref: '#/components/responses/Json429' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/auth/email-otp/capabilities:
    get:
      summary: Read the independent email OTP sign-in capability
      responses: { '200': { $ref: '#/components/responses/Json200' } }
  /api/auth/signin/email-otp/start:
    post:
      summary: Request a short-lived email sign-in code
      description: Requires the exact Origin and rate limits. Existing and unknown emails receive the same response. Codes are peppered HMAC-only, expire after 10 minutes, and require AUTH_EMAIL_OTP_MODE=enabled.
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '202': { $ref: '#/components/responses/Json200' }, '429': { $ref: '#/components/responses/Json429' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/auth/signin/email-otp/complete:
    post:
      summary: Verify a single-use email code and issue a secure session
      description: Requires the exact Origin and rate limits. A challenge permits five attempts and is consumed before session issuance.
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' }, '429': { $ref: '#/components/responses/Json429' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/auth/password/set:
    post:
      summary: Set or rotate a password credential
      description: Requires an authenticated session, exact Origin, CSRF, and recent passkey step-up. Existing password sessions are revoked after replacement; no password or password hash is returned.
      security: [{ SessionCookie: [] }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/auth/step-up/passkey/options:
    post:
      summary: Issue a step-up passkey challenge
      security: [{ SessionCookie: [] }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' } }
  /api/auth/step-up/passkey/complete:
    post:
      summary: Complete passkey step-up
      security: [{ SessionCookie: [] }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' } }
  /api/auth/step-up/password:
    post:
      summary: Complete password re-authentication step-up
      description: Requires an authenticated session, exact Origin, CSRF, a valid server-side password credential, bounded rate limits, and lockout protection. Sets a short-lived step-up timestamp; never issues a session or returns password or hash material.
      security: [{ SessionCookie: [] }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '429': { $ref: '#/components/responses/Json429' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/auth/recovery/start:
    post:
      summary: Start enumeration-resistant recovery proof
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '202': { $ref: '#/components/responses/Json200' }, '429': { $ref: '#/components/responses/Json429' } }
  /api/auth/recovery/resend:
    post:
      summary: Resend a recovery code
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '429': { $ref: '#/components/responses/Json429' } }
  /api/auth/recovery/verify:
    post:
      summary: Verify recovery email proof without issuing a session
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' } }
  /api/auth/recovery/passkey/options:
    post:
      summary: Issue replacement-passkey options after recovery proof
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' } }
  /api/auth/recovery/passkey/complete:
    post:
      summary: Complete replacement-passkey recovery
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' } }
  /api/auth/organizations:
    get:
      summary: List organizations for the current session
      security: [{ SessionCookie: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' } }
  /api/auth/memberships:
    get:
      summary: Read the current Lucion ID's own D1 organization memberships
      description: Returns the authenticated user's active memberships, roles, and groups. The query is derived from the session user and does not accept a tenant selector or expose other users' memberships.
      security: [{ SessionCookie: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/auth/authorization/evaluate:
    post:
      summary: Evaluate an organization permission
      security: [{ SessionCookie: [] }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' } }
  /api/auth/lyra/evaluate:
    post:
      summary: Evaluate a scope-bound Lyra delegation
      security: [{ SessionCookie: [] }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' } }
  /api/auth/lyra/respond:
    post:
      summary: Request a bounded Groq-backed Lyra advisory response
      description: Requires exact-origin CSRF-protected session access, active organization membership, an active delegation for the exact organization, application, agent, and lyra.chat scope, bounded input, and the server-only GROQ_API_KEY. Returns advisory text only; it never executes tools, creates sessions, changes access, or returns a provider response body.
      security: [{ SessionCookie: [] }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '403': { $ref: '#/components/responses/Json403' }, '429': { $ref: '#/components/responses/Json429' }, '502': { $ref: '#/components/responses/Json502' }, '503': { $ref: '#/components/responses/Json503' }, '504': { $ref: '#/components/responses/Json503' } }
  /api/auth/lyra/actions/execute:
    post:
      summary: Submit a confirmed, delegated Lyra action boundary request
      description: Requires exact Origin, CSRF, explicit confirmation, a matching delegation, and Idempotency-Key. The guarded boundary records acceptance but does not execute an external action.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '202': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '403': { $ref: '#/components/responses/Json403' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/organizations:
    get:
      summary: List organizations for the current session
      security: [{ SessionCookie: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' } }
    post:
      summary: Create an organization with an Owner membership
      description: Requires exact Origin, CSRF, recent passkey step-up, and optional hash-only Idempotency-Key.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/organizations/{id}:
    get:
      summary: Read an organization and its tenant-bound summary
      security: [{ SessionCookie: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' } }
    patch:
      summary: Update organization settings
      security: [{ SessionCookie: [] }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/organizations/{id}/members:
    get:
      summary: List tenant-bound members and roles
      security: [{ SessionCookie: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' } }
  /api/organizations/{id}/members/{member_id}:
    patch:
      summary: Update a member role or status
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '409': { $ref: '#/components/responses/Json409' } }
    delete:
      summary: Revoke a membership without deleting the user
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/organizations/{id}/invitations:
    get:
      summary: List tenant-bound invitations
      security: [{ SessionCookie: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' } }
    post:
      summary: Create and deliver an organization invitation
      description: Invitation tokens are hashed and never returned. Delivery responses contain only safe provider classifications.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '202': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' } }
  /api/organizations/{id}/invitations/{invitation_id}:
    post:
      summary: Resend an organization invitation
      description: Rotates the invitation proof hash before delivery; the invitation token is never returned.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      responses: { '201': { $ref: '#/components/responses/Json200' }, '202': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' } }
    delete:
      summary: Revoke an organization invitation
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '404': { $ref: '#/components/responses/Json404' } }
  /api/organizations/invitations/accept:
    post:
      summary: Accept an invitation for the authenticated email
      description: Existing sessions only; acceptance never issues a session.
      security: [{ SessionCookie: [] }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' } }
  /api/organizations/{id}/delegated-admins:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9_.:-]{1,127}$' }
    get:
      summary: List scoped delegated administrator grants
      description: Lists tenant-bound delegated grants without secrets or session material. An active administrator or a grant with the exact delegations.read scope may read the records.
      security: [{ SessionCookie: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' } }
    post:
      summary: Create a scoped delegated administrator grant
      description: Owner-only, exact-origin, CSRF- and recent-passkey-step-up-protected mutation for an existing active member. Scopes must use the documented organization delegation allowlist; expiry is limited to five minutes through 30 days; Owner targets and self-grants are rejected. Idempotency-Key is required. A grant never creates a session or changes the target to Owner.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '403': { $ref: '#/components/responses/Json403' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/organizations/{id}/delegated-admins/{delegation_id}:
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9_.:-]{1,127}$' }
      - name: delegation_id
        in: path
        required: true
        schema: { type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9_.:-]{1,127}$' }
    delete:
      summary: Revoke a delegated administrator grant
      description: Revokes only an active grant in the requested organization. Requires exact-origin CSRF, recent passkey step-up, administrator authorization, and Idempotency-Key. Audit evidence and idempotency completion are committed with the revocation; replay returns no secret or session material.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '404': { $ref: '#/components/responses/Json404' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/organizations/{id}/policies:
    get:
      summary: List organization policies
      security: [{ SessionCookie: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' } }
    post:
      summary: Create or update an organization policy
      description: Supports the optional requireSessionBinding boolean. When enabled, sensitive organization reads, permission evaluation, and organization administrator mutations require the request context to match hash-only IP and user-agent context captured at session issuance. This is session-context binding, not device attestation.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' } }
    patch:
      summary: Update an organization policy
      description: Supports the optional requireSessionBinding boolean. A changed or unavailable session context fails closed for sensitive organization reads, permission evaluation, and organization administrator mutations.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' } }
  /api/organizations/{id}/policies/{policy_key}:
    delete:
      summary: Remove an organization policy and return to the default boundary
      security: [{ SessionCookie: [] }]
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
        - name: policy_key
          in: path
          required: true
          schema: { type: string }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '404': { $ref: '#/components/responses/Json404' } }
  /api/organizations/{id}/groups:
    get:
      summary: List organization groups
      security: [{ SessionCookie: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' } }
    post:
      summary: Create an organization group
      security: [{ SessionCookie: [] }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' } }
  /api/organizations/{id}/groups/{group_id}:
    patch:
      summary: Rename an organization group
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '409': { $ref: '#/components/responses/Json409' } }
    delete:
      summary: Delete an empty organization group
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/organizations/{id}/groups/{group_id}/members:
    get:
      summary: List group members
      security: [{ SessionCookie: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' } }
    post:
      summary: Add an organization member to a group
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/organizations/{id}/groups/{group_id}/members/{membership_id}:
    delete:
      summary: Remove a member from a group
      security: [{ SessionCookie: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '404': { $ref: '#/components/responses/Json404' } }
  /api/organizations/{id}/roles:
    get:
      summary: List organization roles and permissions
      security: [{ SessionCookie: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' } }
    post:
      summary: Create a custom organization role
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' } }
  /api/organizations/{id}/roles/{role_id}:
    patch:
      summary: Update a custom organization role
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '409': { $ref: '#/components/responses/Json409' } }
    delete:
      summary: Delete an unused custom organization role
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/organizations/{id}/applications:
    get:
      summary: List organization application assignments
      security: [{ SessionCookie: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' } }
    post:
      summary: Assign an application to an organization
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' } }
  /api/organizations/{id}/applications/{application_id}:
    delete:
      summary: Disable an organization application assignment
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '404': { $ref: '#/components/responses/Json404' } }
  /api/organizations/{id}/domains:
    get:
      summary: List organization domains
      security: [{ SessionCookie: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' } }
    post:
      summary: Register a DNS-verifiable domain
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/organizations/{id}/domains/{domain_id}:
    delete:
      summary: Delete an organization domain
      description: Deletes a tenant-bound domain record with a replay-safe mutation. A verified domain cannot be deleted while an active federation connection exists; disable federation first. The operation never deletes users or memberships.
      security: [{ SessionCookie: [] }]
      parameters: [{ name: domain_id, in: path, required: true, schema: { type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9_.:-]{1,127}$' } }, { $ref: '#/components/parameters/IdempotencyKey' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '404': { $ref: '#/components/responses/Json404' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/organizations/{id}/domains/{domain_id}/verify:
    post:
      summary: Verify a DNS TXT domain proof
      security: [{ SessionCookie: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '422': { $ref: '#/components/responses/Json422' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/organizations/{id}/federation/connections:
    get:
      summary: List organization federation metadata
      security: [{ SessionCookie: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '503': { $ref: '#/components/responses/Json503' } }
    post:
      summary: Register federation metadata
      description: Metadata is stored disabled and does not enable SAML assertions, OIDC callbacks, domain routing, or just-in-time provisioning. Activation later performs bounded provider metadata validation.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '403': { $ref: '#/components/responses/Json403' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/organizations/{id}/federation/connections/{connection_id}:
    parameters: [{ name: connection_id, in: path, required: true, schema: { type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9_.:-]{1,127}$' } }]
    patch:
      summary: Activate or disable a federation connection
      description: Status changes require exact-origin session mutation protection, CSRF, recent passkey step-up, administrator authorization, and Idempotency-Key. Activating a connection first validates its bounded OIDC JSON or SAML 2.0 IdP metadata; provider failures return a safe classification and do not change state. Active metadata still requires the independent federation protocol gate before sign-in or discovery is available.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '403': { $ref: '#/components/responses/Json403' }, '404': { $ref: '#/components/responses/Json404' }, '409': { $ref: '#/components/responses/Json409' }, '422': { $ref: '#/components/responses/Json422' }, '503': { $ref: '#/components/responses/Json503' } }
    delete:
      summary: Delete a disabled federation connection
      description: Deletion is tenant-bound, replay-safe, and requires the connection to be disabled first. It never deletes users or memberships and never bypasses the independent federation protocol gate.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '404': { $ref: '#/components/responses/Json404' }, '409': { $ref: '#/components/responses/Json409' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/organizations/{id}/federation/connections/{connection_id}/health:
    get:
      summary: Run a read-only provider metadata preflight
      description: Requires tenant-bound session access; a supplied Origin must match the request origin. Re-fetches bounded HTTPS OIDC or SAML IdP metadata and returns only a safe validation status/classification; provider bodies, credentials, claims, tenant identifiers, and session material are never returned. A valid preflight does not enable SAML protocol execution or change connection state.
      security: [{ SessionCookie: [] }]
      parameters: [{ name: connection_id, in: path, required: true, schema: { type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9_.:-]{1,127}$' } }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '404': { $ref: '#/components/responses/Json404' }, '422': { $ref: '#/components/responses/Json422' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/organizations/{id}/lyra/actions:
    get:
      summary: List schema-validated Lyra action catalog entries
      security: [{ SessionCookie: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '503': { $ref: '#/components/responses/Json503' } }
    post:
      summary: Register a schema-validated Lyra action
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '403': { $ref: '#/components/responses/Json403' } }
  /api/organizations/{id}/audit-events:
    get:
      summary: List tenant-bound organization audit events with cursor pagination
      description: Returns stable descending occurred_at/id pages after tenant authorization. Metadata, secrets, tokens, provider bodies, and unrelated organization activity are never returned. Pass the opaque next_cursor unchanged.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/PageLimit' }, { $ref: '#/components/parameters/PageCursor' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '403': { $ref: '#/components/responses/Json403' } }
  /api/organizations/{id}/approvals:
    get:
      summary: List organization approval requests with cursor pagination
      description: Returns stable descending created_at/id pages after tenant authorization. Pass the opaque next_cursor unchanged.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/PageLimit' }, { $ref: '#/components/parameters/PageCursor' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '403': { $ref: '#/components/responses/Json403' } }
    post:
      summary: Create an organization approval request with administrator step-up
      description: Creates a pending record only. Requires exact-origin CSRF protection, recent passkey step-up, Owner/Admin authorization, and separation of duties at decision time. Idempotency-Key is optional but recommended for lost-response retries.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '403': { $ref: '#/components/responses/Json403' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/organizations/{id}/approvals/{approval_id}:
    post:
      summary: Approve or reject a request with independent administrator step-up
      description: Requires exact-origin CSRF protection, recent passkey step-up, Owner/Admin authorization, a pending request, and a different user from the requester. Approval records do not execute the requested action. Idempotency-Key is optional but recommended for lost-response retries.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/developer/applications:
    get:
      summary: List owned applications with cursor pagination
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/PageLimit' }, { $ref: '#/components/parameters/PageCursor' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' } }
    post:
      summary: Register an application
      description: Requires an authenticated Lucion session, exact Origin, CSRF proof, recent step-up, Idempotency-Key, and the per-user application-creation rate limit. Confidential client secrets are generated server-side, shown once, and stored only as one-way verifiers.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '409': { $ref: '#/components/responses/Json409' }, '429': { $ref: '#/components/responses/Json429' } }
  /api/developer/applications/{application_id}:
    parameters: [{ name: application_id, in: path, required: true, schema: { type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9_.:-]{1,127}$' } }]
    patch:
      summary: Edit an owned application
      description: Requires an authenticated Lucion session, exact Origin, CSRF proof, recent step-up, and Idempotency-Key. Only name, description, HTTPS homepage_url, exact redirect_uris, and scopes may change; the client ID and client type are immutable. Returns metadata only and never returns a client secret.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '404': { $ref: '#/components/responses/Json404' }, '409': { $ref: '#/components/responses/Json409' } }
    delete:
      summary: Disable and delete an owned application
      description: Requires an authenticated Lucion session, exact Origin, CSRF proof, recent step-up, and Idempotency-Key. Retains an audit tombstone and revokes the application's client secrets, API keys, OAuth grants/tokens, consents, webhooks, and delegations; it does not delete users. A completed replay returns 409 without secret material.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '404': { $ref: '#/components/responses/Json404' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/developer/applications/{application_id}/rotate-secret:
    parameters: [{ name: application_id, in: path, required: true, schema: { type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9_.:-]{1,127}$' } }]
    post:
      summary: Rotate an owned confidential OIDC client secret
      description: Requires an authenticated Lucion session, exact Origin, CSRF proof, recent step-up, and Idempotency-Key. The JSON request object must be empty. Revokes the previous secret and returns the replacement once; only its hash verifier is stored. A replay returns 409 and never returns the secret again.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '404': { $ref: '#/components/responses/Json404' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/developer/applications/{application_id}/certification/status:
    parameters: [{ name: application_id, in: path, required: true, schema: { type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9_.:-]{1,127}$' } }]
    get:
      summary: Read an owned application certification status
      description: Returns safe signed certification metadata and registered scopes. Evidence and decision tokens are never returned.
      security: [{ SessionCookie: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' }, '404': { $ref: '#/components/responses/Json404' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/v1/developer/applications:
    get:
      summary: List owned applications with a scoped bearer credential
      description: Returns owner-scoped application metadata with bounded opaque cursors. Client secrets and application secret material are never returned.
      security: [{ BearerAuth: [identity.developer.read] }]
      parameters: [{ $ref: '#/components/parameters/PageLimit' }, { $ref: '#/components/parameters/PageCursor' }]
      responses: { '200': { $ref: '#/components/responses/DeveloperApplications200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '503': { $ref: '#/components/responses/Json503' } }
    post:
      summary: Register an application with a scoped API key
      description: Server-to-server mutation for CLI and automation. Requires an API key with identity.applications.manage, an Idempotency-Key, and never returns a client secret to the CLI surface. Browser application registration remains protected by session, exact Origin, CSRF, and passkey step-up.
      security: [{ BearerAuth: [identity.applications.manage] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/developer/api-keys:
    get:
      summary: List owned API-key metadata with cursor pagination
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/PageLimit' }, { $ref: '#/components/parameters/PageCursor' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' } }
    post:
      summary: Create an API key shown once
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/v1/developer/api-keys:
    get:
      summary: List owned API-key metadata with a scoped bearer credential
      description: Returns safe key prefixes, suffixes, scopes, lifecycle timestamps, and bindings. Raw keys and stored hashes are never returned.
      security: [{ BearerAuth: [identity.developer.read] }]
      parameters: [{ $ref: '#/components/parameters/PageLimit' }, { $ref: '#/components/parameters/PageCursor' }]
      responses: { '200': { $ref: '#/components/responses/DeveloperApiKeys200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/developer/audit-events:
    get:
      summary: List owner-scoped audit and security activity
      description: Returns bounded cursor-paginated activity where the authenticated user is the actor or subject, plus that user's security events. Metadata, secrets, tokens, payloads, and provider response bodies are never returned.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/PageLimit' }, { $ref: '#/components/parameters/PageCursor' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' } }
  /api/v1/developer/audit-events:
    get:
      summary: List owner-scoped audit and security activity with a bearer credential
      description: Returns safe activity records for the bearer credential owner with bounded opaque cursors. Metadata, secrets, tokens, payloads, and provider response bodies are never returned.
      security: [{ BearerAuth: [identity.developer.read] }]
      parameters: [{ $ref: '#/components/parameters/PageLimit' }, { $ref: '#/components/parameters/PageCursor' }]
      responses: { '200': { $ref: '#/components/responses/DeveloperAuditEvents200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/developer/api-keys/revoke:
    post:
      summary: Revoke an owned API key
      description: Requires recent passkey step-up. An optional Idempotency-Key makes a retry after a lost response safe; replay returns no credential material.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/developer/webhooks:
    get:
      summary: List owned webhook subscriptions with cursor pagination
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/PageLimit' }, { $ref: '#/components/parameters/PageCursor' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' } }
    post:
      summary: Create a signed outbound webhook subscription
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/v1/developer/webhooks:
    get:
      summary: List owned webhook subscriptions with a scoped bearer credential
      description: Returns safe webhook metadata and parsed event names with bounded opaque cursors. Signing secrets, hashes, and encrypted custody material are never returned.
      security: [{ BearerAuth: [identity.developer.read] }]
      parameters: [{ $ref: '#/components/parameters/PageLimit' }, { $ref: '#/components/parameters/PageCursor' }]
      responses: { '200': { $ref: '#/components/responses/DeveloperWebhooks200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/developer/webhooks/test:
    post:
      summary: Deliver a signed test event
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '409': { $ref: '#/components/responses/Json409' }, '502': { $ref: '#/components/responses/Json502' } }
  /api/v1/developer/webhooks/test:
    post:
      summary: Deliver a signed test event with a scoped API key
      description: Server-to-server mutation for CLI and automation. Requires an API key with identity.webhooks.test and an Idempotency-Key. The event payload and destination response body are never returned.
      security: [{ BearerAuth: [identity.webhooks.test] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '202': { $ref: '#/components/responses/Json200' }, '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '409': { $ref: '#/components/responses/Json409' }, '502': { $ref: '#/components/responses/Json502' } }
  /api/developer/webhooks/deliveries:
    get:
      summary: List owned webhook delivery metadata with cursor pagination
      description: Returns status and safe failure classifications without payloads, secrets, or destination response bodies.
      security: [{ SessionCookie: [] }]
      parameters:
        - name: webhook_id
          in: query
          required: false
          schema: { type: string }
        - { $ref: '#/components/parameters/PageLimit' }
        - { $ref: '#/components/parameters/PageCursor' }
      responses: { '200': { $ref: '#/components/responses/DeveloperWebhookDeliveries200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' } }
  /api/v1/developer/webhooks/deliveries:
    get:
      summary: List owned webhook delivery metadata with a scoped bearer credential
      description: Returns safe delivery status and provider failure classifications with bounded opaque cursors. Payloads, signing secrets, and destination response bodies are never returned.
      security: [{ BearerAuth: [identity.developer.read] }]
      parameters:
        - name: webhook_id
          in: query
          required: false
          schema: { type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9_.:-]{1,127}$' }
        - { $ref: '#/components/parameters/PageLimit' }
        - { $ref: '#/components/parameters/PageCursor' }
      responses: { '200': { $ref: '#/components/responses/DeveloperWebhookDeliveries200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '503': { $ref: '#/components/responses/Json503' } }
  /api/developer/webhooks/deliveries/retry:
    post:
      summary: Queue an owned failed webhook delivery for retry
      description: Requires an authenticated session with recent passkey step-up. Retry is bounded and returns 202 without destination response data.
      security: [{ SessionCookie: [] }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '202': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' }, '404': { $ref: '#/components/responses/Json404' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/developer/webhooks/deliveries/process:
    post:
      summary: Queue due webhook deliveries for the authenticated owner
      description: Requires an authenticated session with recent passkey step-up. Only applications owned by the current user are processed.
      security: [{ SessionCookie: [] }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '202': { $ref: '#/components/responses/Json200' }, '403': { $ref: '#/components/responses/Json403' } }
  /api/developer/webhooks/rotate:
    post:
      summary: Rotate an outbound webhook signing secret safely for retries
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/developer/webhooks/disable:
    post:
      summary: Disable an owned webhook subscription
      description: Requires recent passkey step-up. An optional Idempotency-Key makes a retry after a lost response safe; the signing secret is never returned.
      security: [{ SessionCookie: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '409': { $ref: '#/components/responses/Json409' } }
  /api/webhooks/resend:
    post:
      summary: Receive signed Resend delivery events
      description: Verify Svix headers and the raw body before parsing; raw payloads are not persisted.
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' } }
  /internal/integration-decisions:
    post:
      summary: Consume a signed Lucion Developer certification decision
      description: Server-to-server only. Requires a short-lived Ed25519-signed JWT bound to the app, client, environment, policy, scopes, artifact, reviewer, expiry, sequence, and replay identifier.
      requestBody: { required: true, content: { application/json: { schema: { type: object, required: [decision_jwt], properties: { decision_jwt: { type: string, maxLength: 16384 } }, additionalProperties: false } } } }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' }, '409': { $ref: '#/components/responses/Json409' }, '503': { $ref: '#/components/responses/Json503' } }
  /internal/client-trust/{client_id}:
    parameters: [{ name: client_id, in: path, required: true, schema: { type: string, minLength: 4, maxLength: 200 } }]
    get:
      summary: Read current app trust for a Lucion resource server
      description: Protected service-to-service lookup. Resource servers still validate token issuer, signature, audience, scope, and expiry.
      security: [{ TrustServiceBearer: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' }, '404': { $ref: '#/components/responses/Json404' }, '429': { $ref: '#/components/responses/Json429' }, '503': { $ref: '#/components/responses/Json503' } }
  /oauth/token:
    post:
      summary: Exchange an authorization or rotated refresh token
      description: MCP token exchanges include the exact resource https://identity.lucionglobal.com/mcp; refresh grants rotate and remain bound to that resource.
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' } }
  /oauth/authorize:
    post:
      summary: Issue an authorization code after session consent
      security: [{ SessionCookie: [] }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' } }
  /oauth/authorize/context:
    get:
      summary: Validate an authorization request and return customer-facing consent details
      description: Validates the OAuth client, registered callback, requested scope, PKCE, state, and MCP resource before the consent screen is shown.
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '503': { $ref: '#/components/responses/Json503' } }
  /oauth/register:
    post:
      summary: Register a public OAuth client for an assistant MCP connection
      description: Rate-limited dynamic client registration. Redirect URIs must be HTTPS or loopback HTTP; client secrets are not issued.
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '429': { $ref: '#/components/responses/Json429' }, '503': { $ref: '#/components/responses/Json503' } }
  /oauth/userinfo:
    get:
      summary: Return scoped OIDC claims
      security: [{ BearerAuth: [openid] }]
      responses: { '200': { $ref: '#/components/responses/Json200' } }
  /oauth/revoke:
    post:
      summary: Revoke an OAuth token and its refresh-token family
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' } }
  /mcp:
    post:
      summary: Call Lucion Identity MCP tools with a resource-bound OAuth access token
      description: Requires the identity.api_keys.create scope and MCP resource audience. The create_api_key tool accepts only explicitly requested read-only API-key scopes, a 7/30/90-day lifetime, and an idempotency key; raw key material is returned once and may remain in assistant conversation history.
      security: [{ BearerAuth: [] }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' }, '403': { $ref: '#/components/responses/Json403' }, '413': { description: JSON-RPC body exceeded the 32 KiB request limit }, '415': { description: Request must use application/json }, '429': { $ref: '#/components/responses/Json429' }, '503': { $ref: '#/components/responses/Json503' } }
  /.well-known/oauth-protected-resource/mcp:
    get:
      summary: Read OAuth protected-resource metadata for the MCP server
      responses: { '200': { $ref: '#/components/responses/Json200' } }
  /.well-known/openid-configuration:
    get:
      summary: OIDC discovery metadata
      responses: { '200': { $ref: '#/components/responses/Json200' } }
  /.well-known/oauth-authorization-server:
    get:
      summary: OAuth authorization-server metadata
      responses: { '200': { $ref: '#/components/responses/Json200' } }
  /.well-known/jwks.json:
    get:
      summary: Public OIDC signing keys
      responses: { '200': { $ref: '#/components/responses/Json200' }, '503': { $ref: '#/components/responses/Json503' } }
  /scim/v2/Users:
    get:
      summary: List users in the SCIM token's organization
      description: Supports bounded startIndex/count pagination and exact userName or externalId equality filters.
      security: [{ ScimBearer: [] }]
      parameters:
        - { name: startIndex, in: query, required: false, schema: { type: integer, minimum: 1, maximum: 10000, default: 1 } }
        - { name: count, in: query, required: false, schema: { type: integer, minimum: 0, maximum: 100, default: 100 } }
        - { name: filter, in: query, required: false, description: 'Supported forms: userName eq "email" or externalId eq "value".', schema: { type: string, maxLength: 320 } }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' }, '503': { $ref: '#/components/responses/Json503' } }
    post:
      summary: Provision a user into the SCIM token's organization
      description: Creates an organization-scoped SCIM mapping and membership. Email ownership is immutable and no session or authentication privilege is issued.
      security: [{ ScimBearer: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' }, '409': { $ref: '#/components/responses/Json409' }, '503': { $ref: '#/components/responses/Json503' } }
  /scim/v2/Users/{id}:
    parameters: [{ name: id, in: path, required: true, schema: { type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9_.:-]{1,127}$' } }]
    get:
      summary: Read one organization-scoped SCIM user
      security: [{ ScimBearer: [] }]
      parameters: [{ $ref: '#/components/parameters/IfNoneMatch' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '304': { description: Resource has not changed, headers: { ETag: { $ref: '#/components/headers/EntityTag' } } }, '401': { $ref: '#/components/responses/Json401' }, '404': { $ref: '#/components/responses/Json404' } }
    put:
      summary: Replace one organization-scoped SCIM user
      description: Replaces displayName and active state. userName/email ownership is immutable; externalId is immutable once assigned. If-Match prevents overwriting a newer directory write; reuse the same Idempotency-Key after a timeout.
      security: [{ ScimBearer: [] }]
      parameters: [{ $ref: '#/components/parameters/IfMatch' }, { $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' }, '404': { $ref: '#/components/responses/Json404' }, '409': { $ref: '#/components/responses/Json409' }, '412': { $ref: '#/components/responses/Json412' } }
    patch:
      summary: Patch one organization-scoped SCIM user
      description: Supports up to 20 add/replace/remove operations for active and displayName. Email ownership changes are rejected. If-Match prevents overwriting a newer directory write; reuse the same Idempotency-Key after a timeout.
      security: [{ ScimBearer: [] }]
      parameters: [{ $ref: '#/components/parameters/IfMatch' }, { $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' }, '404': { $ref: '#/components/responses/Json404' }, '412': { $ref: '#/components/responses/Json412' } }
    delete:
      summary: Deprovision one organization-scoped SCIM user
      description: Suspends only the organization membership and retains the underlying Lucion identity and credentials. If-Match prevents deleting a resource changed since it was read; reuse the same Idempotency-Key after a timeout.
      security: [{ ScimBearer: [] }]
      parameters: [{ $ref: '#/components/parameters/IfMatch' }, { $ref: '#/components/parameters/IdempotencyKey' }]
      responses: { '204': { description: User deprovisioned }, '401': { $ref: '#/components/responses/Json401' }, '404': { $ref: '#/components/responses/Json404' }, '412': { $ref: '#/components/responses/Json412' } }
  /scim/v2/Groups:
    get:
      summary: List organization groups
      security: [{ ScimBearer: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '401': { $ref: '#/components/responses/Json401' } }
    post:
      summary: Provision an organization group
      security: [{ ScimBearer: [] }]
      parameters: [{ $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '201': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '409': { $ref: '#/components/responses/Json409' } }
  /scim/v2/Groups/{id}:
    parameters: [{ name: id, in: path, required: true, schema: { type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9_.:-]{1,127}$' } }]
    get:
      summary: Read an organization group
      security: [{ ScimBearer: [] }]
      parameters: [{ $ref: '#/components/parameters/IfNoneMatch' }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '304': { description: Resource has not changed, headers: { ETag: { $ref: '#/components/headers/EntityTag' } } }, '401': { $ref: '#/components/responses/Json401' }, '404': { $ref: '#/components/responses/Json404' } }
    put:
      summary: Replace an organization group
      description: Replaces displayName and the active-user membership set. If-Match prevents overwriting a newer directory write. Reuse the same Idempotency-Key after a timeout; a changed request under the same key is rejected.
      security: [{ ScimBearer: [] }]
      parameters: [{ $ref: '#/components/parameters/IfMatch' }, { $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '404': { $ref: '#/components/responses/Json404' }, '409': { $ref: '#/components/responses/Json409' }, '412': { $ref: '#/components/responses/Json412' } }
    patch:
      summary: Patch an organization group
      description: Updates displayName and membership operations. If-Match prevents overwriting a newer directory write. Reuse the same Idempotency-Key after a timeout; a changed request under the same key is rejected.
      security: [{ ScimBearer: [] }]
      parameters: [{ $ref: '#/components/parameters/IfMatch' }, { $ref: '#/components/parameters/IdempotencyKey' }]
      requestBody: { $ref: '#/components/requestBodies/Json' }
      responses: { '200': { $ref: '#/components/responses/Json200' }, '400': { $ref: '#/components/responses/Json400' }, '404': { $ref: '#/components/responses/Json404' }, '409': { $ref: '#/components/responses/Json409' }, '412': { $ref: '#/components/responses/Json412' } }
    delete:
      summary: Deprovision an organization group
      description: Removes only the group and its group memberships. If-Match prevents deleting a resource changed since it was read. Reuse the same Idempotency-Key after a timeout; a changed request under the same key is rejected.
      security: [{ ScimBearer: [] }]
      parameters: [{ $ref: '#/components/parameters/IfMatch' }, { $ref: '#/components/parameters/IdempotencyKey' }]
      responses: { '204': { description: Group removed }, '401': { $ref: '#/components/responses/Json401' }, '404': { $ref: '#/components/responses/Json404' }, '409': { $ref: '#/components/responses/Json409' }, '412': { $ref: '#/components/responses/Json412' } }
  /scim/v2/ResourceTypes:
    get:
      summary: Read SCIM resource-type metadata
      security: [{ ScimBearer: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' } }
  /scim/v2/Schemas/{id}:
    parameters: [{ name: id, in: path, required: true, schema: { type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9_.:-]{1,127}$' } }]
    get:
      summary: Read one SCIM resource schema
      security: [{ ScimBearer: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' }, '404': { $ref: '#/components/responses/Json404' } }
  /scim/v2/ServiceProviderConfig:
    get:
      summary: Read SCIM service capabilities
      description: Advertises PATCH and ETag support. Resource reads return an opaque ETag and meta.version; If-None-Match supports conditional GET and If-Match protects updates and deprovisioning from stale writers.
      security: [{ ScimBearer: [] }]
      responses: { '200': { $ref: '#/components/responses/Json200' }, '401': { $ref: '#/components/responses/Json401' } }
components:
  securitySchemes:
    BearerAuth: { type: http, scheme: bearer, bearerFormat: OAuth2 access token or lid_live API key }
    SessionCookie: { type: apiKey, in: cookie, name: __Host-lid_session }
    ScimBearer: { type: http, scheme: bearer, description: Organization-bound SCIM token }
    TrustServiceBearer: { type: http, scheme: bearer, description: Server-only trust lookup credential; never send from browser clients }
  requestBodies:
    Json:
      required: true
      content: { application/json: { schema: { type: object, additionalProperties: true } } }
  parameters:
    PageLimit:
      name: limit
      in: query
      required: false
      description: Maximum records to return. Defaults to 100; maximum 100.
      schema: { type: integer, minimum: 1, maximum: 100, default: 100 }
    PageCursor:
      name: cursor
      in: query
      required: false
      description: Opaque continuation token from the previous response. Do not parse or manufacture it.
      schema: { type: string, minLength: 16, maxLength: 512, pattern: '^[A-Za-z0-9_-]+$' }
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: 16–128 character client token. Only a namespace-scoped hash is stored; reuse with a different request returns 409 and one-time secrets are never replayed.
      schema: { type: string, pattern: '^[A-Za-z0-9._:-]{16,128}$' }
    IfMatch:
      name: If-Match
      in: header
      required: false
      description: Strong SCIM entity tag returned by a resource read. Use '*' only when an existing resource may be modified regardless of its current version. A stale tag returns 412 without changing data.
      schema: { type: string, minLength: 1, maxLength: 1024 }
    IfNoneMatch:
      name: If-None-Match
      in: header
      required: false
      description: Entity tag returned by a previous resource read. A matching tag returns 304 without a resource body; weak tags are accepted for conditional reads.
      schema: { type: string, minLength: 1, maxLength: 1024 }
  responses:
    Json200: { description: JSON response, headers: { X-Request-ID: { $ref: '#/components/headers/RequestId' }, X-Lucion-Identity-Version: { $ref: '#/components/headers/ReleaseVersion' }, RateLimit-Limit: { $ref: '#/components/headers/RateLimitLimit' }, RateLimit-Remaining: { $ref: '#/components/headers/RateLimitRemaining' }, RateLimit-Reset: { $ref: '#/components/headers/RateLimitReset' } }, content: { application/json: { schema: { $ref: '#/components/schemas/JsonObject' } } } }
    DeveloperApplications200: { description: Owner-scoped application metadata page, headers: { X-Request-ID: { $ref: '#/components/headers/RequestId' }, X-Lucion-Identity-Version: { $ref: '#/components/headers/ReleaseVersion' }, RateLimit-Limit: { $ref: '#/components/headers/RateLimitLimit' }, RateLimit-Remaining: { $ref: '#/components/headers/RateLimitRemaining' }, RateLimit-Reset: { $ref: '#/components/headers/RateLimitReset' } }, content: { application/json: { schema: { $ref: '#/components/schemas/DeveloperApplicationsResponse' } } } }
    DeveloperApiKeys200: { description: Owner-scoped API-key metadata page, headers: { X-Request-ID: { $ref: '#/components/headers/RequestId' }, X-Lucion-Identity-Version: { $ref: '#/components/headers/ReleaseVersion' }, RateLimit-Limit: { $ref: '#/components/headers/RateLimitLimit' }, RateLimit-Remaining: { $ref: '#/components/headers/RateLimitRemaining' }, RateLimit-Reset: { $ref: '#/components/headers/RateLimitReset' } }, content: { application/json: { schema: { $ref: '#/components/schemas/DeveloperApiKeysResponse' } } } }
    DeveloperAuditEvents200: { description: Owner-scoped audit activity page, headers: { X-Request-ID: { $ref: '#/components/headers/RequestId' }, X-Lucion-Identity-Version: { $ref: '#/components/headers/ReleaseVersion' }, RateLimit-Limit: { $ref: '#/components/headers/RateLimitLimit' }, RateLimit-Remaining: { $ref: '#/components/headers/RateLimitRemaining' }, RateLimit-Reset: { $ref: '#/components/headers/RateLimitReset' } }, content: { application/json: { schema: { $ref: '#/components/schemas/DeveloperAuditEventsResponse' } } } }
    DeveloperWebhooks200: { description: Owner-scoped webhook subscription page, headers: { X-Request-ID: { $ref: '#/components/headers/RequestId' }, X-Lucion-Identity-Version: { $ref: '#/components/headers/ReleaseVersion' }, RateLimit-Limit: { $ref: '#/components/headers/RateLimitLimit' }, RateLimit-Remaining: { $ref: '#/components/headers/RateLimitRemaining' }, RateLimit-Reset: { $ref: '#/components/headers/RateLimitReset' } }, content: { application/json: { schema: { $ref: '#/components/schemas/DeveloperWebhooksResponse' } } } }
    DeveloperWebhookDeliveries200: { description: Owner-scoped webhook delivery metadata page, headers: { X-Request-ID: { $ref: '#/components/headers/RequestId' }, X-Lucion-Identity-Version: { $ref: '#/components/headers/ReleaseVersion' }, RateLimit-Limit: { $ref: '#/components/headers/RateLimitLimit' }, RateLimit-Remaining: { $ref: '#/components/headers/RateLimitRemaining' }, RateLimit-Reset: { $ref: '#/components/headers/RateLimitReset' } }, content: { application/json: { schema: { $ref: '#/components/schemas/DeveloperWebhookDeliveriesResponse' } } } }
    Json400: { description: Invalid request or verification failure, headers: { X-Request-ID: { $ref: '#/components/headers/RequestId' }, X-Lucion-Identity-Version: { $ref: '#/components/headers/ReleaseVersion' } }, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
    Json401: { description: Authentication required or token invalid, headers: { X-Request-ID: { $ref: '#/components/headers/RequestId' }, X-Lucion-Identity-Version: { $ref: '#/components/headers/ReleaseVersion' }, WWW-Authenticate: { schema: { type: string } } }, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
    Json409: { description: Replay or resource conflict, headers: { X-Request-ID: { $ref: '#/components/headers/RequestId' }, X-Lucion-Identity-Version: { $ref: '#/components/headers/ReleaseVersion' } }, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
    Json412: { description: SCIM resource changed since the supplied If-Match value, headers: { X-Request-ID: { $ref: '#/components/headers/RequestId' }, X-Lucion-Identity-Version: { $ref: '#/components/headers/ReleaseVersion' }, ETag: { $ref: '#/components/headers/EntityTag' } }, content: { application/scim+json: { schema: { $ref: '#/components/schemas/ScimError' } } } }
    Json403: { description: Origin, CSRF, tenant, or scope boundary denied, headers: { X-Request-ID: { $ref: '#/components/headers/RequestId' }, X-Lucion-Identity-Version: { $ref: '#/components/headers/ReleaseVersion' } }, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
    Json429: { description: Authentication-risk rate limit exceeded, headers: { X-Request-ID: { $ref: '#/components/headers/RequestId' }, X-Lucion-Identity-Version: { $ref: '#/components/headers/ReleaseVersion' }, RateLimit-Limit: { $ref: '#/components/headers/RateLimitLimit' }, RateLimit-Remaining: { $ref: '#/components/headers/RateLimitRemaining' }, RateLimit-Reset: { $ref: '#/components/headers/RateLimitReset' }, Retry-After: { $ref: '#/components/headers/RetryAfter' } }, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
    Json502: { description: Bounded outbound webhook delivery failed, headers: { X-Request-ID: { $ref: '#/components/headers/RequestId' }, X-Lucion-Identity-Version: { $ref: '#/components/headers/ReleaseVersion' } }, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
    Json503: { description: Capability or dependency is gated/unavailable, headers: { X-Request-ID: { $ref: '#/components/headers/RequestId' }, X-Lucion-Identity-Version: { $ref: '#/components/headers/ReleaseVersion' } }, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
    Json406: { description: Requested API version is not supported, headers: { X-Request-ID: { $ref: '#/components/headers/RequestId' }, X-Lucion-Identity-Version: { $ref: '#/components/headers/ReleaseVersion' }, X-Lucion-API-Version: { $ref: '#/components/headers/ApiVersion' }, X-Lucion-Supported-API-Versions: { $ref: '#/components/headers/SupportedApiVersions' } }, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
  headers:
    RequestId: { description: Server-generated correlation identifier; safe to quote in support requests., schema: { type: string, minLength: 16, maxLength: 32 } }
    ReleaseVersion: { description: Deployed Lucion Identity release fingerprint., schema: { type: string, pattern: '^[0-9]+\\.[0-9]+\\.[0-9]+$' } }
    ApiVersion: { description: Current version of the route family, such as v1 or scim-2.0., schema: { type: string, pattern: '^(v[0-9]+|scim-[0-9]+\\.[0-9]+)$' } }
    ApiLifecycle: { description: Lifecycle state of the advertised API version., schema: { type: string, enum: [current, deprecated] } }
    SupportedApiVersions: { description: Comma-separated supported versions for the route family., schema: { type: string, minLength: 2, maxLength: 64 } }
    EntityTag: { description: Opaque strong SCIM resource version used for conditional reads and stale-write protection., schema: { type: string, pattern: '^"[A-Za-z0-9_-]{3,128}"$' } }
    RateLimitLimit: { description: Maximum attempts in the endpoint-specific authentication-risk window., schema: { type: integer, minimum: 1 } }
    RateLimitRemaining: { description: Remaining attempts in the current endpoint-specific window., schema: { type: integer, minimum: 0 } }
    RateLimitReset: { description: Seconds until the endpoint-specific window resets., schema: { type: integer, minimum: 0 } }
    RetryAfter: { description: Seconds to wait before retrying a throttled request., schema: { type: integer, minimum: 1 } }
  schemas:
    JsonObject: { type: object, additionalProperties: true }
    Error:
      type: object
      required: [error]
      properties:
        error: { type: string, description: Stable non-secret error code }
        retry_after_seconds: { type: integer }
        resource_type: { type: string }
        resource_id: { type: string }
        failure_code: { type: string }
        supported_versions: { type: array, items: { type: string } }
        current_version: { type: string }
        documentation: { type: string }
    ScimError:
      type: object
      required: [schemas, status, detail]
      properties:
        schemas: { type: array, items: { type: string } }
        status: { type: string }
        scimType: { type: string }
        detail: { type: string }
      additionalProperties: true
    DeveloperApplication:
      type: object
      required: [id, name, client_id, application_type, status, description, homepage_url, token_endpoint_auth_method, created_at, updated_at]
      properties:
        id: { type: string }
        name: { type: string }
        client_id: { type: string }
        application_type: { type: string, enum: [public, confidential, service] }
        status: { type: string }
        description: { type: [string, 'null'] }
        homepage_url: { type: [string, 'null'], format: uri }
        token_endpoint_auth_method: { type: string }
        allowed_emails: { type: array, maxItems: 100, description: Session-owner console metadata for confidential apps; omitted from scoped bearer reads., items: { type: string, format: email } }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
      additionalProperties: false
    DeveloperApiKey:
      type: object
      required: [id, name, key_prefix, key_suffix, scopes, expires_at, created_at, last_used_at, revoked_at, application_id, organization_id]
      properties:
        id: { type: string }
        name: { type: string }
        key_prefix: { type: string }
        key_suffix: { type: string }
        scopes: { type: array, items: { type: string } }
        expires_at: { type: [string, 'null'], format: date-time }
        created_at: { type: string, format: date-time }
        last_used_at: { type: [string, 'null'], format: date-time }
        revoked_at: { type: [string, 'null'], format: date-time }
        application_id: { type: [string, 'null'] }
        organization_id: { type: [string, 'null'] }
      additionalProperties: false
    DeveloperAuditEvent:
      type: object
      required: [id, occurred_at, actor_type, actor_id, action, resource_type, resource_id, outcome]
      properties:
        id: { type: string }
        occurred_at: { type: string, format: date-time }
        actor_type: { type: string }
        actor_id: { type: string }
        subject_user_id: { type: [string, 'null'] }
        organization_id: { type: [string, 'null'] }
        application_id: { type: [string, 'null'] }
        action: { type: string }
        scope_key: { type: [string, 'null'] }
        resource_type: { type: string }
        resource_id: { type: string }
        outcome: { type: string }
      additionalProperties: false
    DeveloperWebhook:
      type: object
      required: [id, application_id, endpoint_url, events, status, created_at, updated_at, last_success_at, last_failure_at]
      properties:
        id: { type: string }
        application_id: { type: string }
        endpoint_url: { type: string, format: uri }
        events: { type: array, items: { type: string } }
        status: { type: string, enum: [active, disabled] }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        last_success_at: { type: [string, 'null'], format: date-time }
        last_failure_at: { type: [string, 'null'], format: date-time }
      additionalProperties: false
    DeveloperWebhookDelivery:
      type: object
      required: [id, event_id, event_type, status, attempts, created_at, updated_at]
      properties:
        id: { type: string }
        event_id: { type: string }
        event_type: { type: string }
        status: { type: string }
        attempts: { type: integer, minimum: 0 }
        next_attempt_at: { type: [string, 'null'], format: date-time }
        last_error_code: { type: [string, 'null'] }
        response_status: { type: [integer, 'null'], minimum: 100, maximum: 599 }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        delivered_at: { type: [string, 'null'], format: date-time }
      additionalProperties: false
    DeveloperApplicationsResponse: { type: object, required: [applications, has_more, next_cursor, authentication, scopes], properties: { applications: { type: array, items: { $ref: '#/components/schemas/DeveloperApplication' } }, has_more: { type: boolean }, next_cursor: { type: [string, 'null'] }, authentication: { type: string, enum: [api_key, oauth] }, scopes: { type: array, items: { type: string } } }, additionalProperties: false }
    DeveloperApiKeysResponse: { type: object, required: [api_keys, has_more, next_cursor, authentication, scopes], properties: { api_keys: { type: array, items: { $ref: '#/components/schemas/DeveloperApiKey' } }, has_more: { type: boolean }, next_cursor: { type: [string, 'null'] }, authentication: { type: string, enum: [api_key, oauth] }, scopes: { type: array, items: { type: string } } }, additionalProperties: false }
    DeveloperAuditEventsResponse: { type: object, required: [audit_events, has_more, next_cursor, authentication, scopes], properties: { audit_events: { type: array, items: { $ref: '#/components/schemas/DeveloperAuditEvent' } }, has_more: { type: boolean }, next_cursor: { type: [string, 'null'] }, authentication: { type: string, enum: [api_key, oauth] }, scopes: { type: array, items: { type: string } } }, additionalProperties: false }
    DeveloperWebhooksResponse: { type: object, required: [webhooks, has_more, next_cursor, authentication, scopes], properties: { webhooks: { type: array, items: { $ref: '#/components/schemas/DeveloperWebhook' } }, has_more: { type: boolean }, next_cursor: { type: [string, 'null'] }, authentication: { type: string, enum: [api_key, oauth] }, scopes: { type: array, items: { type: string } } }, additionalProperties: false }
    DeveloperWebhookDeliveriesResponse: { type: object, required: [deliveries, has_more, next_cursor, authentication, scopes], properties: { deliveries: { type: array, items: { $ref: '#/components/schemas/DeveloperWebhookDelivery' } }, has_more: { type: boolean }, next_cursor: { type: [string, 'null'] }, authentication: { type: string, enum: [api_key, oauth] }, scopes: { type: array, items: { type: string } } }, additionalProperties: false }
