> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sente.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Registrations

> Create and re-login accounts at third-party apps, manage credentials, and open logged-in browser sessions.

A registration is an [identity's](/api-reference/identities) account at one app, keyed by the app's URL origin. There are two kinds: `created` (Sente registered the account) and `connected` (you own the account and connected it — see [Connections](/api-reference/connections)). Creating or logging in queues a [run](/api-reference/runs): a browser agent drives the app's own signup/login forms and completes email verification from the identity's own inbox.

<Note>
  Register only at apps whose terms permit automated account creation. If you want a human to approve the final step, pass `confirmBeforeSubmit: true` — the agent fills the form, then the run blocks with a live view so a person clicks submit. CAPTCHAs and SMS verification are not automated: they block the run for human takeover. See [Acceptable use](/trust/acceptable-use) and [Limits](/trust/limits).
</Note>

## The registration object

```json theme={null}
{
  "id": "reg_1c2d3e4f5a6b708192a3b4c5d6e7f801",
  "identityId": "idt_9f1c2ab34d5e46f7a8b9c0d1e2f3a4b5",
  "appUrl": "https://app.example.com/signup",
  "kind": "created",
  "username": "acme-qa@sente.run",
  "status": "pending",
  "revokedAt": null,
  "createdAt": "2026-07-20T11:00:00.000Z"
}
```

| Field      | Description                                                                                                                     |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `kind`     | `created` (Sente made the account) or `connected` (customer-owned).                                                             |
| `status`   | `pending` (no confirmed account yet) → `active` (a run confirmed login) \| `failed`. `disabled` means a connection was revoked. |
| `username` | The username at the app. Passwords are never in this object — see credentials below.                                            |

One registration exists per (identity, app origin): `https://app.example.com/signup` and `https://app.example.com/` are the same registration; `example.com` and `app.example.com` are different ones.

## Register at an app

`POST /v1/registrations`

| Field                 | Type                       | Required | Description                                                                                                                              |
| --------------------- | -------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `identityId`          | string                     | yes      | The identity that will own the account.                                                                                                  |
| `appUrl`              | URL                        | yes      | Where to sign up.                                                                                                                        |
| `credentials`         | `{ username?, password? }` | no       | Use these at the app. Omitted password → Sente generates a strong one and vaults it.                                                     |
| `confirmBeforeSubmit` | boolean                    | no       | Agent fills the form, then blocks (`SUBMIT_CONFIRMATION_REQUIRED`) with a live view so a human clicks the final submit. Default `false`. |

Returns the registration plus the run driving it. The call is idempotent and converging: re-posting the same (identity, app) returns the existing registration — joining an in-flight run if one exists, and never re-driving an already-`active` account (use `/login` to re-authenticate).

```bash theme={null}
curl -X POST https://api.sente.run/v1/registrations \
  -H "Authorization: Bearer sk_sente_..." \
  -H "content-type: application/json" \
  -d '{
    "identityId": "idt_9f1c2ab34d5e46f7a8b9c0d1e2f3a4b5",
    "appUrl": "https://app.example.com/signup",
    "confirmBeforeSubmit": true
  }'
```

Response `201`:

```json theme={null}
{
  "registration": {
    "id": "reg_1c2d3e4f5a6b708192a3b4c5d6e7f801",
    "identityId": "idt_9f1c2ab34d5e46f7a8b9c0d1e2f3a4b5",
    "appUrl": "https://app.example.com/signup",
    "kind": "created",
    "username": null,
    "status": "pending",
    "revokedAt": null,
    "createdAt": "2026-07-20T11:00:00.000Z"
  },
  "run": {
    "id": "run_8d9e0f1a2b3c4d5e6f708192a3b4c5d6",
    "type": "register",
    "status": "queued",
    "registrationId": "reg_1c2d3e4f5a6b708192a3b4c5d6e7f801",
    "error": null,
    "result": null,
    "liveViewUrl": null,
    "gatewaySessionId": null,
    "createdAt": "2026-07-20T11:00:00.000Z",
    "endedAt": null
  }
}
```

Poll [`GET /v1/runs/:id`](/api-reference/runs) or subscribe to [`run.*` webhooks](/api-reference/webhooks) for the outcome.

Errors:

| Status | Code                 | When                                                                                                                                |
| ------ | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `429`  | `RUN_LIMIT_EXCEEDED` | Daily or monthly run cap.                                                                                                           |
| `409`  | `ALREADY_CONNECTED`  | This identity already has a **connected** (customer-owned) account at that origin. `register` never drives over an owner's account. |
| `404`  | —                    | `identityId` not your org's.                                                                                                        |

## List registrations

`GET /v1/registrations` — optional `?identityId=...` to scope to one identity.

Each row embeds `latestRun` (a [run object](/api-reference/runs), or `null`) for status display.

```bash theme={null}
curl "https://api.sente.run/v1/registrations?identityId=idt_9f1c2ab34d5e46f7a8b9c0d1e2f3a4b5" \
  -H "Authorization: Bearer sk_sente_..."
```

Response `200`:

```json theme={null}
[
  {
    "id": "reg_1c2d3e4f5a6b708192a3b4c5d6e7f801",
    "identityId": "idt_9f1c2ab34d5e46f7a8b9c0d1e2f3a4b5",
    "appUrl": "https://app.example.com/signup",
    "kind": "created",
    "username": "acme-qa@sente.run",
    "status": "active",
    "revokedAt": null,
    "createdAt": "2026-07-20T11:00:00.000Z",
    "latestRun": {
      "id": "run_8d9e0f1a2b3c4d5e6f708192a3b4c5d6",
      "type": "register",
      "status": "completed",
      "registrationId": "reg_1c2d3e4f5a6b708192a3b4c5d6e7f801",
      "error": null,
      "result": { "answer": "STATE: LOGGED_IN" },
      "liveViewUrl": "https://app.hyperbrowser.ai/live?...",
      "gatewaySessionId": "3f6a...",
      "createdAt": "2026-07-20T11:00:00.000Z",
      "endedAt": "2026-07-20T11:03:12.500Z"
    }
  }
]
```

## Get a registration

`GET /v1/registrations/:id`

```bash theme={null}
curl https://api.sente.run/v1/registrations/reg_1c2d3e4f5a6b708192a3b4c5d6e7f801 \
  -H "Authorization: Bearer sk_sente_..."
```

Response `200`: the registration object. `404` otherwise.

## Log in again

`POST /v1/registrations/:id/login`

Queues a `login` run that re-authenticates the account with the vaulted credentials (completing email verification from the identity's inbox if the app sends one). Works for both `created` and `connected` registrations.

```bash theme={null}
curl -X POST https://api.sente.run/v1/registrations/reg_1c2d3e4f5a6b708192a3b4c5d6e7f801/login \
  -H "Authorization: Bearer sk_sente_..."
```

Response `201`: `{ "registration": { ... }, "run": { ..., "type": "login", "status": "queued" } }`

Errors: `429 RUN_LIMIT_EXCEEDED`; `409 {"code": "REVOKED"}` if this is a revoked connection.

## Open a logged-in browser session

`POST /v1/registrations/:id/session`

Opens a browser on the identity's persistent profile — already logged in to this app — and returns a CDP URL for `puppeteer.connect` / `chromium.connectOverCDP`. See [Sessions](/guides/sessions).

By default the login is **verified by recency**: if the last confirmed login is fresh (within \~10 minutes), the session opens immediately; if it's older, Sente queues a re-login run first and answers `409 SESSION_STALE` with the `runId` to poll — retry once that run completes.

| Body field    | Type    | Description                                                        |
| ------------- | ------- | ------------------------------------------------------------------ |
| `verify`      | boolean | `false` skips the freshness check and opens the raw session as-is. |
| `maxStaleSec` | number  | Override the freshness window in seconds.                          |

```bash theme={null}
curl -X POST https://api.sente.run/v1/registrations/reg_1c2d3e4f5a6b708192a3b4c5d6e7f801/session \
  -H "Authorization: Bearer sk_sente_..." \
  -H "content-type: application/json" \
  -d '{}'
```

Response `201`:

```json theme={null}
{
  "sessionId": "3f6a9b2c-...",
  "cdpUrl": "wss://connect.hyperbrowser.ai?token=...",
  "liveViewUrl": "https://app.hyperbrowser.ai/live?...",
  "expiresAt": "2026-07-20T11:25:00.000Z",
  "verified": true,
  "healed": false
}
```

The `cdpUrl` is returned once and never stored. One live session per identity. Errors (`409`, with `code`):

| Code                   | Meaning                                                                              |
| ---------------------- | ------------------------------------------------------------------------------------ |
| `SESSION_STALE`        | Login too old — a re-login run was queued. Poll the returned `runId`, then retry.    |
| `SESSION_PENDING`      | A run is already in flight for this identity. Poll the returned `runId`, then retry. |
| `SESSION_ALREADY_OPEN` | This identity already has an open session (returns its `sessionId`). Close it first. |
| `IDENTITY_BUSY`        | A run is using the identity's browser profile right now.                             |
| `NOT_ACTIVE`           | The registration has no confirmed account (`status` isn't `active`).                 |
| `NO_PROFILE`           | No browser profile exists yet — no run has ever logged this identity in.             |

## Close the session

`DELETE /v1/registrations/:id/session`

Stops and releases the identity's open browser session. Idempotent.

```bash theme={null}
curl -X DELETE https://api.sente.run/v1/registrations/reg_1c2d3e4f5a6b708192a3b4c5d6e7f801/session \
  -H "Authorization: Bearer sk_sente_..."
```

Response: `204` with no body.

## Export session state

`POST /v1/registrations/:id/session/export`

Returns a portable Playwright `storageState` (cookies + localStorage) for the logged-in account, loadable into your own Playwright, Puppeteer, or browser-use — no Sente browser needed afterwards. Accepts the same `verify` / `maxStaleSec` body and the same `409 SESSION_STALE` / `SESSION_PENDING` contract as `/session`. If a session is already open it exports from that; otherwise Sente opens one, exports, and closes it.

```bash theme={null}
curl -X POST https://api.sente.run/v1/registrations/reg_1c2d3e4f5a6b708192a3b4c5d6e7f801/session/export \
  -H "Authorization: Bearer sk_sente_..." \
  -H "content-type: application/json" \
  -d '{}'
```

Response `200`:

```json theme={null}
{
  "storageState": {
    "cookies": [ { "name": "session", "value": "...", "domain": "app.example.com", "path": "/", "expires": 1795000000, "httpOnly": true, "secure": true, "sameSite": "Lax" } ],
    "origins": [ { "origin": "https://app.example.com", "localStorage": [ { "name": "token", "value": "..." } ] } ]
  }
}
```

<Warning>
  The export is a bearer credential for a logged-in account. Treat it as a secret. Every export is written to the audit log.
</Warning>

## Get credentials

`GET /v1/registrations/:id/credentials`

For **Sente-created** accounts only: returns the vaulted username and password in plaintext (you need the password Sente generated). Every read is audited.

```bash theme={null}
curl https://api.sente.run/v1/registrations/reg_1c2d3e4f5a6b708192a3b4c5d6e7f801/credentials \
  -H "Authorization: Bearer sk_sente_..."
```

Response `200`:

```json theme={null}
{
  "username": "acme-qa@sente.run",
  "password": "kV9#mQ2...",
  "origin": "generated"
}
```

`origin` is `generated` (Sente made the password) or `supplied` (you provided it).

For **connected** accounts this endpoint returns `403 {"error": "connected-account credentials are write-only", "code": "CREDENTIALS_WRITE_ONLY"}` — you already have your own password, and Sente never reads a customer's credential back out. See [Connections](/api-reference/connections).

## Update credentials

`PUT /v1/registrations/:id/credentials`

| Field      | Type   | Required | Description            |
| ---------- | ------ | -------- | ---------------------- |
| `username` | string | no       | New username to vault. |
| `password` | string | no       | New password to vault. |

Overwrites the **vault only** — it does not change the password at the app. Use it after you rotate the password at the app yourself, so the next login run uses the new one.

```bash theme={null}
curl -X PUT https://api.sente.run/v1/registrations/reg_1c2d3e4f5a6b708192a3b4c5d6e7f801/credentials \
  -H "Authorization: Bearer sk_sente_..." \
  -H "content-type: application/json" \
  -d '{"password": "new-password-set-at-the-app"}'
```

Response: `204` with no body. `409 {"code": "REVOKED"}` if the registration is a revoked connection.
