# Login codes and sessions

Your server makes a one-time login code. The embed trades it for a session and keeps that session fresh by itself.

## From your page to a session

Four parties take part. Your partner key never leaves your server, and the session never leaves the frame.

1. **Your page → GoRunner** mount() opens the frame and checks this page may show itGET /api/embed/v1/bootstrap
2. **Your page → Your server** getCode() asks your server for a login code
3. **Your server → GoRunner** Your server asks for a code, with your partner keyPOST /api/embed/v1/login-codes
4. **GoRunner → Your server** A login code: one use, 60 secondsgrl_…
5. **Your server → Your page** Your server hands the page the code, and only the code
6. **Your page → GoRunner frame** The SDK passes the code into the frame
7. **GoRunner frame → GoRunner** The frame trades the code for a sessionPOST /api/embed/v1/sessions
8. **GoRunner → GoRunner frame** A session, held in memory for 30 minutesgri_…
9. **GoRunner frame → Your page** Ready: mount() resolves and the customer sees GoRunner

## Login codes

Your server makes a code with [Make a login code](https://docs.gorunner.io/embed/api/sign-in/issue-login-code), naming the person, their account and their role: `viewer`, `operator` or `editor`. The first code for a new account or person creates them.

A code starts with `grl_`, works once, and runs out after 60 seconds. Never log it. One person may get 20 codes a minute.

## Sessions

The frame holds its session (`gri_…`) in memory, never in a cookie. It lasts 30 minutes and is never extended: a renewal is a new session. Each browser tab has its own, so two tabs can be in two accounts. Only hashes of codes and sessions are kept.

## Renewal

Five minutes before a session ends, the frame swaps it for a new one, and the customer notices nothing.

- **By itself**: For 12 hours after the login code, the frame renews its own session (`POST /api/embed/v1/sessions/current/renew`). The new one keeps the person, account, workspace, role, every switch turned off, the key and the page. The old one ends (`replaced`). Your server isn’t asked.
- **Through your page**: After the 12 hours, the SDK calls your `getCode` with the reason `renew`. So does a refused renewal: a suspended account, a Platform preview, a key revoked or past its rotation, or a website no longer allowed for the account. A tab that slept past its renewal asks too, and a reloaded frame asks with `reload`.
- **On time**: Each session carries `renewAfter` and `renewInSeconds`. The frame counts the seconds, so a wrong device clock still renews on time.
- **Limits**: At most 4 renewals a minute from one login code (`EMBED_RENEW_RATE_LIMIT`, [Rate limits](https://docs.gorunner.io/embed/api#limits)). A self-hosted installation can set the 12 hours from 1 to 24 (`EMBED_SESSION_MAX_HOURS`).
- **Older installations**: Before v2026.10.4 the frame never renews by itself: it asks your page every 25 minutes.

## When a session ends

The server ends a session early with a reason (`details.reason`). Your page never sees most reasons: the SDK asks your `getCode` for a new code, and the customer notices nothing. What your page sees, and the reasons behind it:

- **A new `getCode` call**: With the reason `renew` or `expired`, and no event. Ended by `expired`, `replaced`, `role_changed`, `account_suspended`, `account_resumed` or `revoked`.
- `access_ended`: One fatal `error` event; the embed closes for good. Ended by `access_removed`, `account_deleted`, `workspace_deleted` or `website_removed`.
- `licence` or `unavailable`: One fatal `error` event: the license lost Embed, or the installation’s sign-in is off. Ended by `not_licensed` or `sign_in_off`.
- **Nothing**: The frame signed itself out, on `unmount()` or an account switch: `ended`.

For what each code means for your page, see [The error event](https://docs.gorunner.io/embed/events#error).

A request with an ended session answers `401 EMBED_SESSION_ENDED`, the reason in `details.reason`, and an open stream closes with it. `not_licensed` and `sign_in_off` answer `403 EMBED_NOT_AVAILABLE`. An embed session is refused everywhere outside the embed’s own routes (`403 EMBED_NOT_ALLOWED`).

A suspended account is the one renewal your page hears about. Once the new session finds the account suspended, the embed sends an `error` event with `code: "account_suspended"` and `fatal: false`. The customer can still read everything.

## Related

- [Quick start](https://docs.gorunner.io/embed/quick-start): A partner key, a login-code route and the embed in a page · 15 min
- [SDK](https://docs.gorunner.io/embed/sdk): getCode, events and errors
- [Security](https://docs.gorunner.io/embed/security): Keys, websites and what never reaches the browser

Source: https://docs.gorunner.io/embed/sign-in-and-sessions
