Skip to main content
Sign a user into the developer portal from your backend, with no second login. After they sign in to your app, ask Unkey for a session for that user and redirect their browser to the URL you get back. Your root key never reaches the browser.

Sign a user in

1

Your backend mints a session

Call portal.createSession with the portal, the user’s externalId, and the scopes they should have. You get { "id": "ps_...", "url": "https://portal.unkey.com/?code=pec_..." }. The URL works once, within 15 minutes. To put the URL on your own domain, pass a verified custom domain as hostname.
2

Redirect the user

Send the browser to url.
3

The portal exchanges the code

The portal swaps the code for an access token that lasts 24 hours. If the code is expired, already used, or unknown, the user sees err:unkey:authentication:portal_session_not_found, “Session is invalid, expired, or has already been used.”
4

The user works, then leaves

The user only sees their own keys. When the token expires, or they click the return link, the portal sends them to the returnUrl you set. Without one, there’s no return link. To let them back in, create a new session.

Request

Create a root key with the permissions listed on this page under Root Keys in the dashboard sidebar. CLI commands can instead use a session from unkey login with the required permissions. See Permission reference for every permission.
string
required
The portal’s prt_ ID or slug.
string
required
The user’s ID in your system, 1 to 256 characters. A session for user_123 shows the keys whose identity is user_123.
string[]
required
One or more of keys:read, keys:reroll, and analytics:read. Scopes decide what the user can see and do. See What portal users can do. keys:reroll and analytics:read each need keys:read too. Without it, the request fails with 400 and err:unkey:application:invalid_input.
string
Full URL, up to 500 characters, to send the user back to when they leave or the session expires. You set it per session, so different users can go back to different pages.
boolean
default:"false"
Create a preview session to try the portal yourself.

Permissions your root key needs

Your root key needs unkey:v1:<workspace_id>:projects/<project_id>/portals/<portal_id>/sessions/*#write. Write permission on the portal itself does not grant permission to mint sessions. Without the session permission, the request fails with 404, or with 403 naming the missing permission if your root key can read the portal or its sessions. It also needs the following grants for each scope on every keyspace the portal shows. Each resource in the table has the prefix unkey:v1:<workspace_id>:projects/<project_id>/keyspaces/<keyspace_id>: Broader URN grants that cover these resources and actions are also accepted. A grant on one concrete key cannot satisfy the keys/* requirement. If the root key is missing a scope’s grant, the whole request fails with 403. A disabled portal can’t create sessions at all.

What ends a session

A session ends when its 24 hour token expires, or when the portal is disabled, deleted, or pointed at a different keyspace or app. That usually takes effect within about 10 seconds, but can take up to 5 minutes. Re-enabling a disabled portal doesn’t bring its sessions back. To log one end user out early, call portal.revokeSession with the portal and their externalId. It ends every live session they have on that portal. Revoking sessions requires the same unkey:v1:<workspace_id>:projects/<project_id>/portals/<portal_id>/sessions/*#write grant as creating a session, or a broader grant that includes it. A root key or dashboard session can call this endpoint with that permission. Permission on the portal itself does not grant permission to revoke sessions. Without read access to the portal’s sessions, the request fails with 404. With read access but without write access, it fails with 403 naming the missing permission. To see who is signed in, call portal.listSessions with the portal. It returns one entry per end user with a session that has not expired or been revoked, including sessions whose portal URL was not opened yet, and each end user’s sessions and scopes. Results are ordered by externalId, paginated with cursor, and can be filtered with a case-sensitive search prefix. It needs unkey:v1:<workspace_id>:projects/<project_id>/portals/<portal_id>/sessions/*#read or the same resource with #write. Reading the portal itself is not enough. Without the session permission, the request fails with 404. Creating, exchanging, and revoking sessions write portal.session.create, portal.session.exchange, and portal.session.revoke audit log entries. What the user does in the portal is logged under a portalEndUser actor.

Errors

Last modified on November 13, 2026