> ## 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.

# Connections

> Connect accounts you already own for delegated agent access — write-only credentials, TOTP vaulting, revoke and delete.

A connection gives Sente delegated access to an account **you already own**: you supply the credentials, Sente logs in with a browser run, keeps the session re-loginable, and vaults the credentials write-only. It is the owner-authorized counterpart to [registering](/api-reference/registrations) a new account. See the [Connect guide](/guides/connect).

A connection is stored as a registration with `kind: "connected"`, so it shares the registration endpoints: sessions, login, and session export live at `/v1/registrations/:id/...` using the connection's id.

<Note>
  Known limits, stated up front:

  * **SSO-only accounts cannot be connected.** If the account signs in only via Google/Okta/etc. or passkeys, there is no username+password for Sente to use.
  * **Authenticator (TOTP) 2FA re-logins autonomously** — supply `totpSeed` and the server computes the 30-second code at login. The seed is vaulted encrypted and **never reaches any model**; only the server-side controller computes codes from it.
  * **Email-code MFA needs a human** in the interactive live view — unless you repoint the account's notification email to the identity's Sente address and pass `verifyToIdentity: true`, in which case the code lands in Sente's inbox and is injected automatically.
  * **SMS codes always need a human.** The run blocks (`MFA_REQUIRED`) and pages one.
</Note>

## Create a connection

`POST /v1/connections`

| Field                  | Type    | Required | Description                                                                                                                                                                  |
| ---------------------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `identityId`           | string  | yes      | The identity this connection hangs off.                                                                                                                                      |
| `appUrl`               | URL     | yes      | The app to log in to.                                                                                                                                                        |
| `credentials.username` | string  | yes      | Your username at the app.                                                                                                                                                    |
| `credentials.password` | string  | yes      | Your password. Vaulted encrypted, write-only.                                                                                                                                |
| `credentials.totpSeed` | string  | no       | The authenticator secret — bare base32 or a full `otpauth://` URI. Enables autonomous 2FA re-login.                                                                          |
| `verifyToIdentity`     | boolean | no       | You have repointed this account's notification email to the identity's Sente address, so emailed login/2FA codes can be fetched and injected automatically. Default `false`. |

Queues a `connect` run that logs in with the supplied credentials and marks the connection `active` on success. Re-posting for the same (identity, app origin) **re-connects**: rotates the vaulted credentials, clears any revoke, and re-drives — this is also how you rotate a password or re-enable a revoked connection.

```bash theme={null}
curl -X POST https://api.sente.run/v1/connections \
  -H "Authorization: Bearer sk_sente_..." \
  -H "content-type: application/json" \
  -d '{
    "identityId": "idt_9f1c2ab34d5e46f7a8b9c0d1e2f3a4b5",
    "appUrl": "https://app.example.com",
    "credentials": {
      "username": "me@mycompany.com",
      "password": "my-real-password",
      "totpSeed": "JBSWY3DPEHPK3PXP"
    }
  }'
```

Response `201`:

```json theme={null}
{
  "connection": {
    "id": "reg_2d3e4f5a6b7c808192a3b4c5d6e7f812",
    "identityId": "idt_9f1c2ab34d5e46f7a8b9c0d1e2f3a4b5",
    "appUrl": "https://app.example.com",
    "kind": "connected",
    "username": "me@mycompany.com",
    "status": "pending",
    "revokedAt": null,
    "createdAt": "2026-07-21T08:30:00.000Z"
  },
  "run": {
    "id": "run_9e0f1a2b3c4d5e6f708192a3b4c5d6e7",
    "type": "connect",
    "status": "queued",
    "registrationId": "reg_2d3e4f5a6b7c808192a3b4c5d6e7f812",
    "error": null,
    "result": null,
    "liveViewUrl": null,
    "gatewaySessionId": null,
    "createdAt": "2026-07-21T08:30:00.000Z",
    "endedAt": null
  }
}
```

Errors:

| Status | Code                 | When                                                                                                                 |
| ------ | -------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `400`  | `BAD_TOTP_SEED`      | The `totpSeed` isn't usable base32 / `otpauth://`. Rejected at enrollment rather than failing three re-logins later. |
| `409`  | `ALREADY_REGISTERED` | A **Sente-created** account already exists at that origin for this identity. `connect` never overwrites it.          |
| `429`  | `RUN_LIMIT_EXCEEDED` | Daily or monthly run cap.                                                                                            |
| `404`  | —                    | `identityId` not your org's.                                                                                         |

(The mirror-image conflict — `register` against an existing connection — is `409 ALREADY_CONNECTED` on [`POST /v1/registrations`](/api-reference/registrations).)

## List connections

`GET /v1/connections` — optional `?identityId=...`. Returns only `kind: "connected"` rows, each with `latestRun`.

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

Response `200`:

```json theme={null}
[
  {
    "id": "reg_2d3e4f5a6b7c808192a3b4c5d6e7f812",
    "identityId": "idt_9f1c2ab34d5e46f7a8b9c0d1e2f3a4b5",
    "appUrl": "https://app.example.com",
    "kind": "connected",
    "username": "me@mycompany.com",
    "status": "active",
    "revokedAt": null,
    "createdAt": "2026-07-21T08:30:00.000Z",
    "latestRun": { "id": "run_9e0f1a2b3c4d5e6f708192a3b4c5d6e7", "type": "connect", "status": "completed", "registrationId": "reg_2d3e4f5a6b7c808192a3b4c5d6e7f812", "error": null, "result": null, "liveViewUrl": "https://app.hyperbrowser.ai/live?...", "gatewaySessionId": "5c1d...", "createdAt": "2026-07-21T08:30:00.000Z", "endedAt": "2026-07-21T08:33:40.000Z" }
  }
]
```

## Get a connection

`GET /v1/connections/:id`

Returns the connection object. `404` for unknown ids, other orgs' resources, **and** for registrations that aren't `connected`.

## Credentials are write-only

There is no credential read for connections. `GET /v1/registrations/:id/credentials` on a connected account returns:

```json theme={null}
{ "error": "connected-account credentials are write-only", "code": "CREDENTIALS_WRITE_ONLY" }
```

with status `403`. You already hold your own password; Sente vaults it encrypted and only the server-side run controller ever decrypts it to log in. To rotate a credential, re-post `POST /v1/connections` with the new one.

## Revoke a connection

`POST /v1/connections/:id/revoke`

Withdraws the delegation: the connection becomes `disabled` — login and credential updates answer `409 REVOKED`, and sessions can no longer open (`409 NOT_ACTIVE`). The vault is **kept**, so re-enabling is a single `POST /v1/connections` away.

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

Response `200`:

```json theme={null}
{
  "id": "reg_2d3e4f5a6b7c808192a3b4c5d6e7f812",
  "identityId": "idt_9f1c2ab34d5e46f7a8b9c0d1e2f3a4b5",
  "appUrl": "https://app.example.com",
  "kind": "connected",
  "username": "me@mycompany.com",
  "status": "disabled",
  "revokedAt": "2026-07-25T14:00:00.000Z",
  "createdAt": "2026-07-21T08:30:00.000Z"
}
```

## Delete a connection

`DELETE /v1/connections/:id`

Revoke **and purge**: the vaulted password and TOTP seed are erased. This is the offboarding guarantee — after delete, Sente holds no secrets for this account. The row itself remains (audit trail), still `disabled`.

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

Response: `204` with no body.

|                                       | Revoke                                        | Delete                                        |
| ------------------------------------- | --------------------------------------------- | --------------------------------------------- |
| Delegation                            | withdrawn                                     | withdrawn                                     |
| Vaulted secrets (password, TOTP seed) | kept, encrypted                               | purged                                        |
| Re-enable                             | `POST /v1/connections` again with credentials | `POST /v1/connections` again with credentials |

Use revoke to pause a delegation you expect to restore; use delete when offboarding and Sente should hold nothing.
