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

# Validate an OAuth token

> Exchanges the single-use `token` returned to your `redirectUrl` after a
Google or Apple sign-in for a session.

The response depends on whether the environment requires two-factor
authentication. See the `EndUserAuthResult` schema for the three shapes
and how to branch on them.




## OpenAPI

````yaml /openapi/authentication-api.yaml post /oauth/tokens
openapi: 3.1.0
info:
  title: Portal Authentication API
  version: '3.0'
  description: |
    The Portal Authentication API signs in your end users with an email magic
    link, Google, or Apple. A completed sign-in returns a Client Session Token
    that your app uses to create and operate the user's wallet.

    ## Base URL
    `https://api.portalhq.io/api/v3/auth`

    ## Authentication
    These endpoints do not use a Portal API Key or a Client Session Token.
    Identify your environment with the `x-portal-auth-environment-id` header,
    set to the Auth Environment ID shown in the Portal dashboard under
    **Authentication > Configure**.

    Requests are rejected with `401` if the header is missing, if it does not
    match an environment, or if authentication is not enabled for that
    environment.

    ## Response format
    Successful responses are wrapped in a `data` object, alongside a `metadata`
    field that is `null` for every endpoint in this API:

    ```json
    { "data": { "sent": true }, "metadata": null }
    ```

    ## Error responses
    Every authentication failure returns the same generic `401` body, with no
    indication of which check failed. This is deliberate, so a caller cannot
    distinguish an expired token from a forged one. Treat any `401` as "start
    the sign-in flow again".
servers:
  - url: https://api.portalhq.io/api/v3/auth
    description: Production
security:
  - authEnvironmentId: []
tags:
  - name: Auth Methods
    description: Discover which sign-in methods an environment has enabled
  - name: Magic Links
    description: Send and validate email magic links
  - name: OAuth
    description: Sign in with Google or Apple
  - name: Two-Factor Authentication
    description: Validate a time-based one-time password (TOTP) code
paths:
  /oauth/tokens:
    post:
      tags:
        - OAuth
      summary: Validate an OAuth token
      description: |
        Exchanges the single-use `token` returned to your `redirectUrl` after a
        Google or Apple sign-in for a session.

        The response depends on whether the environment requires two-factor
        authentication. See the `EndUserAuthResult` schema for the three shapes
        and how to branch on them.
      operationId: validateOAuthToken
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TokenValidationRequest'
            example:
              token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.example.token
      responses:
        '200':
          description: Token validated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EndUserAuthResponse'
              example:
                data:
                  endUserId: clx1enduser00000000000000
                  clientSessionToken: b7c1f0a2-3d4e-5f60-8a91-2b3c4d5e6f70
                  userJwt: null
                  totpLink: null
                metadata: null
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
components:
  schemas:
    TokenValidationRequest:
      type: object
      required:
        - token
      properties:
        token:
          type: string
          description: |
            The single-use `token` query parameter Portal appended to your
            redirect URL.
    EndUserAuthResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/EndUserAuthResult'
        metadata:
          type:
            - object
            - 'null'
          description: Always `null` for this endpoint.
    EndUserAuthResult:
      type: object
      description: |
        The outcome of a sign-in. Which fields are set depends on whether the
        environment requires two-factor authentication:

        - **Two-factor not required.** `clientSessionToken` is set, `userJwt`
          and `totpLink` are `null`. The sign-in is complete.
        - **Two-factor required, user already enrolled.** `userJwt` is set,
          `clientSessionToken` and `totpLink` are `null`. Prompt for a code and
          call `POST /totps/validations`.
        - **Two-factor required, first sign-in.** `userJwt` and `totpLink` are
          both set, `clientSessionToken` is `null`. Render `totpLink` as a QR
          code for the user to scan, then prompt for a code and call
          `POST /totps/validations`.

        Branch on `clientSessionToken` being non-null to decide whether the
        sign-in is finished.
      properties:
        endUserId:
          type: string
          description: The Portal end user's ID. Stable across sign-ins.
        clientSessionToken:
          type:
            - string
            - 'null'
          description: |
            The Client Session Token for this user's client, or `null` when a
            second factor is still outstanding.
        userJwt:
          type:
            - string
            - 'null'
          description: |
            Single-use credential authorizing the TOTP step, or `null` when no
            second factor is required.
        totpLink:
          type:
            - string
            - 'null'
          description: |
            An `otpauth://` URI to render as a QR code, returned only on the
            user's first two-factor sign-in. `null` otherwise.
    ErrorResponse:
      type: object
      properties:
        error:
          type: string
          description: Error message describing what went wrong
        details:
          type: object
          description: Additional context about the failure, when available
        code:
          type: string
          description: Machine-readable error code, when available
  responses:
    BadRequest:
      description: Bad request. A required field is missing or malformed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: email must be a valid email address.
    Unauthorized:
      description: |
        Unauthorized. Returned for every authentication failure, with no
        indication of which check failed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: Unauthorized
  securitySchemes:
    authEnvironmentId:
      type: apiKey
      in: header
      name: x-portal-auth-environment-id
      description: |
        The Auth Environment ID for the environment you are authenticating
        against. Find it in the Portal dashboard under
        **Authentication > Configure**.

````