> ## Documentation Index
> Fetch the complete documentation index at: https://unkey-eng-3082-add-portal-config-crud-api-endpoints-v2portal.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# portal_session_not_found

> The provided exchange code or access token was not found, has expired, or has already been redeemed. Create a new session from your backend and redirect the user again.

<Danger>`err:unkey:authentication:portal_session_not_found`</Danger>

```json Example theme={"theme":"kanagawa-wave"}
{
  "meta": {
    "requestId": "req_2c9a0jf23l4k567"
  },
  "error": {
    "detail": "Session is invalid, expired, or has already been used.",
    "status": 401,
    "title": "Unauthorized",
    "type": "https://unkey.com/docs/errors/unkey/authentication/portal_session_not_found"
  }
}
```

## What Happened?

This error is returned by `POST /v2/portal.exchangeCode` and any portal-authenticated endpoint when the supplied credential cannot be resolved to a usable session. There are four common reasons:

* **Expired code**: The exchange code carried by the portal URL is valid for **15 minutes**. After that it can no longer be redeemed.
* **Already-redeemed code**: Codes are **single-use**. Once the portal redeems one, the same code cannot be redeemed again.
* **Expired access token**: After exchange, the access token is valid for **24 hours**. Once it expires, requests using it return this error.
* **Revoked session**: A session that has been explicitly revoked returns this error even before its natural expiry.

The exchange endpoint deliberately does not distinguish between these cases in its response. An unknown code, an expired one, and one that was already redeemed all produce the same message, so a caller cannot probe which codes exist.

## How To Fix

Create a fresh session from your backend and redirect the user again:

```bash theme={"theme":"kanagawa-wave"}
curl -X POST https://api.unkey.com/v2/portal.createSession \
  -H "Authorization: Bearer YOUR_ROOT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "portal": "my-portal",
    "externalId": "user_123",
    "scopes": ["keys:read", "analytics:read"]
  }'
```

Then redirect the user to the returned `url`. The portal will redeem the new code for a 24-hour access token.

Re-authenticating always mints a new session rather than extending the existing one, so this is the correct response to every case above.

If you passed a `returnUrl` when creating the session, expired sessions will automatically redirect there with `?reason=session_expired`. Use that hook to re-mint a session and bounce the user back into the portal seamlessly.

```typescript theme={"theme":"kanagawa-wave"}
// In your backend route handler
if (request.url.searchParams.get("reason") === "session_expired") {
  const { data } = await createPortalSession(currentUser);
  return Response.redirect(data.url, 302);
}
```

## Common Mistakes

* **Reusing a code**: Codes are single-use. Generate a new session for every redirect.
* **Storing codes**: Don't persist exchange codes. They are short-lived credentials meant to be consumed immediately, and they are not recoverable from Unkey once issued.
* **Confusing `id` with the code**: `portal.createSession` returns a non-secret `id` alongside the `url`. The `id` identifies the session for your own records; it is not a credential and cannot be exchanged.
* **Long-running tabs**: Users who keep the portal open beyond 24 hours need a fresh session.

## Related Errors

* [err:unkey:authentication:portal\_token\_missing](./portal_token_missing) - When no portal access token is supplied at all
* [err:unkey:data:portal\_not\_found](../data/portal_not_found) - When the portal referenced by `portal` does not exist
* [err:unkey:authentication:missing](./missing) - When no authentication credentials are provided to a non-portal endpoint
