> ## Documentation Index
> Fetch the complete documentation index at: https://docs.causeloop.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Exchange

> AuthBridge: exchange an upstream IdP token for a scoped CauseLoop JWT.

Deny-by-default. Verification is real, not stubbed:
  - The configured identity provider (``build_provider()``) verifies
    ``subject_token`` via ``provider.verify()`` (signature + issuer +
    audience/client_id, returning normalized claims or None). A failed
    verification is a 401.
  - ``resolve_admission`` decides admit/deny against durable provisioning
    state (existing membership, pending invitation, or domain-JIT only when
    explicitly enabled). An unprovisioned identity is denied: we write an
    ``auth.login.denied`` audit event and return 403 — we never JIT-create a
    user as a side effect of exchange.
  - On admit we load the resolved workspace, mint a scoped token via
    sign_access_token (scopes derive from PERMISSIONS), and write an
    ``auth.login.admitted`` audit event.

Dev fallback: when no provider is configured and ENVIRONMENT != "production"
we preserve the legacy seed-user behavior so local/mock dev keeps working
(this path does NOT run admission). When no provider is configured and
ENVIRONMENT == "production" we refuse (503).
(identity-architecture.md §3.1 token-exchange grant)



## OpenAPI

````yaml /openapi.json post /v1/auth/exchange
openapi: 3.1.0
info:
  description: Causeloop Platform Backend
  title: Causeloop API
  version: 1.0.0
servers: []
security: []
paths:
  /v1/auth/exchange:
    post:
      tags:
        - auth
      summary: Exchange
      description: >-
        AuthBridge: exchange an upstream IdP token for a scoped CauseLoop JWT.


        Deny-by-default. Verification is real, not stubbed:
          - The configured identity provider (``build_provider()``) verifies
            ``subject_token`` via ``provider.verify()`` (signature + issuer +
            audience/client_id, returning normalized claims or None). A failed
            verification is a 401.
          - ``resolve_admission`` decides admit/deny against durable provisioning
            state (existing membership, pending invitation, or domain-JIT only when
            explicitly enabled). An unprovisioned identity is denied: we write an
            ``auth.login.denied`` audit event and return 403 — we never JIT-create a
            user as a side effect of exchange.
          - On admit we load the resolved workspace, mint a scoped token via
            sign_access_token (scopes derive from PERMISSIONS), and write an
            ``auth.login.admitted`` audit event.

        Dev fallback: when no provider is configured and ENVIRONMENT !=
        "production"

        we preserve the legacy seed-user behavior so local/mock dev keeps
        working

        (this path does NOT run admission). When no provider is configured and

        ENVIRONMENT == "production" we refuse (503).

        (identity-architecture.md §3.1 token-exchange grant)
      operationId: exchange_v1_auth_exchange_post
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExchangeRequest'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
          description: Successful Response
        '422':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
          description: Validation Error
components:
  schemas:
    ExchangeRequest:
      description: >-
        AuthBridge: exchange an upstream JWT (WorkOS/SAML/OIDC) for a Causeloop
        JWT.
      properties:
        subject_token:
          title: Subject Token
          type: string
        subject_token_type:
          default: urn:ietf:params:oauth:token-type:jwt
          title: Subject Token Type
          type: string
        workspace_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Workspace Id
      required:
        - subject_token
      title: ExchangeRequest
      type: object
    TokenResponse:
      properties:
        access_token:
          title: Access Token
          type: string
        expires_in:
          default: 3600
          title: Expires In
          type: integer
        id_token:
          anyOf:
            - type: string
            - type: 'null'
          title: Id Token
        refresh_token:
          title: Refresh Token
          type: string
        scope:
          default: ''
          title: Scope
          type: string
        session_id:
          default: ''
          title: Session Id
          type: string
        token_type:
          default: Bearer
          title: Token Type
          type: string
      required:
        - access_token
        - refresh_token
      title: TokenResponse
      type: object
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          title: Detail
          type: array
      title: HTTPValidationError
      type: object
    ValidationError:
      properties:
        ctx:
          title: Context
          type: object
        input:
          title: Input
        loc:
          items:
            anyOf:
              - type: string
              - type: integer
          title: Location
          type: array
        msg:
          title: Message
          type: string
        type:
          title: Error Type
          type: string
      required:
        - loc
        - msg
        - type
      title: ValidationError
      type: object

````