Skip to content

Sign in to the customer portal with a link

The portal.sessions.redeem operation: POST /api/v1/public/portal_sessions/{portalSessionId}/redeem in the REST API and the portal_sessions_redeem tool in the MCP server.

POST/api/v1/public/portal_sessions/{portalSessionId}/redeem

Redeems a one-time sign-in link (from the portal's email, or an operator's portal.sessions.create): the link's portal session becomes an active session for 7 days, and the response carries its sessionToken once. The portal keeps it in an HttpOnly, Secure, SameSite=Lax cookie on its own host; only its hash is stored.

A link works once and for 15 minutes. A used, expired or signed-out link, or a wrong token, is portal_link_invalid, all alike. The customer's portalLastLoginAt is set.

Public: the session's own brand and mode apply (a test-mode link signs in to the test-mode portal).

At a glance

  • REST: POST /api/v1/public/portal_sessions/{portalSessionId}/redeem
  • MCP tool: portal_sessions_redeem
  • Operation ID: portal.sessions.redeem
  • Scope: Brand
  • Permission: portal:write
  • Risk tier: security
  • Dry run: Not supported
  • Idempotency: Idempotency-Key required
  • Modes: Test and live
  • Authentication: None (a public endpoint, rate-limited per IP address)
  • Since: M9

Request

Path parameters

FieldTypeDescription
portalSessionIdpsess_… IDThe portal session's ID (psess_…).

Body

A JSON object (Content-Type: application/json):

FieldTypeRequiredDescription
tokenstringYesThe token of the sign-in link.

Response

200 OK with a JSON object:

FieldTypeDescription
object"portal_session"
idpsess_… ID
customerIdcus_… ID
brandIdbrd_… ID
brandSlugstring
livemodeboolean
sessionTokenstringThe session's secret for the portal's HttpOnly cookie. Returned once; only its hash is stored.
expiresAtdate-timeThe session ends then (7 days), or at sign-out.

Examples

HTTP
POST /api/v1/public/portal_sessions/psess_01m4tx5nj8emwbyf7y9vcf48b2/redeem
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "token": "Q2hhbmdlTWVJbkV4YW1wbGVzT25seS4uLi4uLi4uLi4"
}

Response (200):

JSON
{
  "object": "portal_session",
  "id": "psess_01m4tx5nj8emwbyf7y9vcf48b2",
  "customerId": "cus_01m4tx5nj8emwbyf7y9vcf48b1",
  "brandId": "brd_01m4tx5nj8emwbyf7y9vcf48b0",
  "brandSlug": "genno",
  "livemode": false,
  "sessionToken": "U2Vzc2lvblRva2VuRm9yRG9jc09ubHkuLi4uLi4uLi4",
  "expiresAt": "2026-10-27T09:01:00.000Z"
}

Errors

CodeHTTP statusWhenWhat to do
validation_failed400portalSessionId or token is malformed.Fix the fields listed in details.issues and send the request again.
resource_not_found404No portal session has this ID.Check the ID and that your credentials are scoped to the right organization, entity, brand and mode.
portal_link_invalid401The link was already used, expired (15 minutes), was signed out of, or the token is wrong.Ask for a new sign-in link on the portal's sign-in page. Links work once, for 15 minutes; an operator can also generate a new one.
customer_deleted400The customer was deleted after the link was sent.Create a new customer for this buyer. Deleted customers stay readable for the records they belong to, but can't be changed.
rate_limited429Too many requests from this address or client IP; wait retry_after_ms.Wait for retry_after_ms and retry with backoff.

It can also return these common errors: api_version_unsupported, invalid_json, unsupported_media_type, scope_required, idempotency_key_required, idempotency_key_reused, idempotency_in_progress, dry_run_not_supported, internal_error.