> ## 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.

# Send a magic link

> Emails a sign-in link to the end user. Portal sends the email from a
verified sending domain using one of your email templates.

Three of the fields must already be configured in the dashboard:

- `redirectUrl` must exactly match one of the environment's Redirect URLs.
- `fromEmail` must belong to a verified sending domain that is enabled
  for this environment.
- `templateId` is the Portal template ID from
  **Authentication > Templates**.




## OpenAPI

````yaml /openapi/authentication-api.yaml post /magic-links
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:
  /magic-links:
    post:
      tags:
        - Magic Links
      summary: Send a magic link
      description: >
        Emails a sign-in link to the end user. Portal sends the email from a

        verified sending domain using one of your email templates.


        Three of the fields must already be configured in the dashboard:


        - `redirectUrl` must exactly match one of the environment's Redirect
        URLs.

        - `fromEmail` must belong to a verified sending domain that is enabled
          for this environment.
        - `templateId` is the Portal template ID from
          **Authentication > Templates**.
      operationId: sendMagicLink
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendMagicLinkRequest'
            example:
              email: user@example.com
              redirectUrl: https://example.com/callback
              fromEmail: hello@auth.example.com
              templateId: clx1template000000000000
      responses:
        '200':
          description: Magic link sent successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SendMagicLinkResponse'
              example:
                data:
                  sent: true
                metadata: null
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          description: |
            Unauthorized. Returned when the environment header is invalid, when
            `redirectUrl` is not on the environment's allow list, when
            `fromEmail` does not belong to a sending domain enabled for this
            environment, or when the template does not belong to your custodian.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: Unauthorized
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalServerError'
components:
  schemas:
    SendMagicLinkRequest:
      type: object
      required:
        - email
        - redirectUrl
        - fromEmail
        - templateId
      properties:
        email:
          type: string
          format: email
          description: |
            The end user's email address. Must be lowercase. Addresses
            containing uppercase characters are rejected with `400`.
        redirectUrl:
          type: string
          format: uri
          description: |
            Where the magic link sends the user. Must exactly match one of the
            environment's Redirect URLs. Matching is exact string equality, so
            wildcards and path prefixes are not supported.
        fromEmail:
          type: string
          format: email
          description: |
            The address the email is sent from. Must belong to a verified
            sending domain that is enabled for this environment.
        templateId:
          type: string
          description: |
            The Portal template ID from **Authentication > Templates** in the
            dashboard.
    SendMagicLinkResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            sent:
              type: boolean
              description: |
                Always `true`. Confirms Portal accepted the request and handed
                the email off for delivery. It does not confirm the user
                received it.
        metadata:
          type:
            - object
            - 'null'
          description: Always `null` for this endpoint.
    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.
    RateLimited:
      description: Too many requests. Retry after a short delay.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: Too many requests, please try again later
    InternalServerError:
      description: Internal server error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: Internal server error
  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**.

````