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

# Create Portal Token

> Mint a short-lived JWT to redirect the user to the billing portal.

Creates a signed JWT for accessing the self-service billing portal — where tenants can view invoices, update payment methods, manage seats, and cancel their subscription.

The tenant must already exist (i.e. have gone through checkout at least once). Returns `404` if the tenant is not found.

## Request Headers

<ParamField header="x-public-key" type="string" required>
  Your public key (`pk_live_...`)
</ParamField>

## Request Body

<ParamField body="externalTenantId" type="string" required>
  The tenant ID from your application
</ParamField>

<ParamField body="returnUrl" type="string" required>
  URL to return to when the user exits the portal. Must be a valid URL.
</ParamField>

<ParamField body="metadata" type="object">
  Optional context stored with the token. Supports `userEmail`, `userName`.
</ParamField>

## Response

<ResponseField name="data.token" type="string">
  Signed JWT (valid for 10 minutes)
</ResponseField>

<ResponseField name="meta.portalUrl" type="string">
  The full portal URL with token — redirect the user here
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST "https://app.crovver.com/api/public/auth/portal-token" \
    -H "x-public-key: pk_live_..." \
    -H "Content-Type: application/json" \
    -d '{
      "externalTenantId": "workspace_123",
      "returnUrl": "https://app.com/settings/billing"
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 201 theme={null}
  {
    "success": true,
    "data": {
      "token": "eyJhbGciOiJIUzI1NiJ9...",
      "expiresAt": "2025-01-01T00:10:00Z"
    },
    "meta": {
      "portalUrl": "https://portal.crovver.com/billing?token=eyJhbGci...",
      "expiresIn": 600
    }
  }
  ```

  ```json 404 Tenant not found theme={null}
  {
    "success": false,
    "data": null,
    "error": {
      "message": "Tenant not found. Use the checkout flow to create a subscription first.",
      "code": "TENANT_NOT_FOUND"
    }
  }
  ```
</ResponseExample>
