# CLI session endpoints

> The routes behind geni login, geni workspace, geni exec, and geni browser. They take a CLI session only, never an API key.

Source: https://docs.generalinput.com/api/cli-session



Four route families exist for `geni` itself. They need a switchable, revocable session with a person behind it, so an API key is refused with `401 Authentication required`: the key simply is not a session, and the route says nothing more. They are documented here so the shape of the CLI is not a mystery, not as an integration surface.

The message `403 This endpoint requires a CLI session` does exist, but it guards exactly one other mount reached through a different prefix.

<Access />

A CLI session (`geni_rs_…`) is minted by the device-code flow, lives in `~/.config/geni/runner-session.json`, and is bound to one account with a switchable active workspace. Everything else under `/v1` accepts it as a full-access member credential.

## Device-code login (`/v1/auth`) [#device-code-login-v1auth]

| Method | Path                                     | Auth              | Does                                                                                                |
| ------ | ---------------------------------------- | ----------------- | --------------------------------------------------------------------------------------------------- |
| POST   | `/v1/auth/device-code`                   | none              | Start a login. Returns `userCode`, `deviceCode`, `verificationUri`, `intervalSeconds`, `expiresAt`. |
| GET    | `/v1/auth/device-code/:userCode`         | none              | The code's `status`: `pending`, `approved`, `denied`, `expired`. Read-only.                         |
| POST   | `/v1/auth/device-code/:userCode/poll`    | none              | CLI-only poll. On `approved`, consumes and returns the one-shot `sessionToken`.                     |
| POST   | `/v1/auth/device-code/:userCode/approve` | dashboard session | The browser side. Body `membershipId`.                                                              |
| POST   | `/v1/auth/device-code/:userCode/deny`    | dashboard session | The browser side.                                                                                   |
| GET    | `/v1/auth/me`                            | CLI session       | The user and active workspace behind `geni auth status`.                                            |
| POST   | `/v1/auth/logout`                        | CLI session       | Revoke the session.                                                                                 |

Codes live about ten minutes. The unauthenticated routes are limited to 600 requests per minute per IP.

## Workspaces (`/v1/workspaces`) [#workspaces-v1workspaces]

| Method | Path                     | Does                                                                          |
| ------ | ------------------------ | ----------------------------------------------------------------------------- |
| GET    | `/v1/workspaces`         | Every workspace the account belongs to, with `isActive`.                      |
| GET    | `/v1/workspaces/members` | Active members of the active workspace. `?query=` and `?page=` (25 per page). |
| POST   | `/v1/workspaces/switch`  | Re-point the session at another membership. Body `membershipId`.              |

## Exec (`/v1/exec`) [#exec-v1exec]

| Method | Path               | Does                                                                                                                                                                                |
| ------ | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| POST   | `/v1/exec/resolve` | Resolve declared credentials for one `geni exec bash` call: env vars, values to scrub, a fresh platform key, and the egress policy. Body `credentials: [{ id, reason }]`, up to 50. |

The subprocess runs on the operator's machine. The server only hands back env vars and redaction values, and writes the credential access log. A credential the member cannot use fails the whole call with `403`; a credential that resolved to an empty token (an expired OAuth refresh) is reported in `errors[]` without aborting.

## Browser (`/v1/browser`) [#browser-v1browser]

| Method | Path                                     | Does                                                               |
| ------ | ---------------------------------------- | ------------------------------------------------------------------ |
| GET    | `/v1/browser/profiles`                   | The member's browser plus shared ones, with saved logins.          |
| POST   | `/v1/browser/sessions`                   | Open a hosted browser for local CDP scripts. Optional `profileId`. |
| POST   | `/v1/browser/sessions/:token/touch`      | Refresh the liveness lease. `404` once the session is gone.        |
| POST   | `/v1/browser/sessions/:token/close`      | Save the login and stop billing.                                   |
| POST   | `/v1/browser/profiles/:id/report-login`  | Record a listed login as `active` or `logged_out`.                 |
| POST   | `/v1/browser/profiles/:id/login-session` | Start the interactive login handoff. Owner only.                   |
| POST   | `…/login-session/:token/touch`           | Keep the handoff alive.                                            |
| POST   | `…/login-session/:token/check`           | Ask whether the site is signed in. Starts an agent check.          |
| GET    | `…/login-session/:token/outcome`         | The last check's verdict. Poll until `isRunning` is false.         |
| POST   | `…/login-session/:token/finish`          | Verify and save the login.                                         |
| POST   | `…/login-session/:token/cancel`          | Abandon the handoff.                                               |

Sessions bill browser time, so a stateless key has no business here. See [`geni browser`](/cli/browser) for the flow these back.
