Skip to main content
@sente-labs/sdk is a thin typed client for the Sente REST API. Zero runtime dependencies (it uses global fetch). Types ship with the package — no @types/* install.

Setup

Get an API key from the dashboard (API keys page), or via the CLI with sente login + sente token. The key authenticates your whole organization — keep it in an environment variable, never in code.
new Sente(opts) takes { apiKey, baseUrl?, fetch? }. baseUrl defaults to https://api.sente.run; the fetch override is handy for tests.

Create an identity

An identity is a real, reusable email address on sente.run that your agent owns. See Identities and registrations.
If the localPart is taken, create throws a 409 (onConflict: "error" is the default); pass onConflict: "suffix" to append a random suffix instead.

Wait for a verification code

Every inbound email is classified server-side (message.annotation: { kind, code, link, confidence }), so an agent that signed up somewhere with the identity’s address can block until the code or link arrives. Stamp since before triggering the action so you never match an older email:
Both resolve null on timeout. timeout is in seconds (1–60, default 25). If you omit since, the server falls back to a 60-second lookback. More patterns in Receive verification codes.

Send and reply

text and/or html is required. To consume mail continuously, messages.stream(identityId) is an async generator that long-polls; for production prefer webhooks (push, not poll).

Register at a third-party app

registrations.register drives a browser through signup at appUrl under the identity’s email and completes email verification from the identity’s own inbox. It returns the durable account (registration) and its first run.
Register only at apps whose terms permit it. confirmBeforeSubmit: true keeps a human in the loop: the agent fills the form, then the run goes blocked with error.code === "SUBMIT_CONFIRMATION_REQUIRED" and a liveViewUrl — a person clicks the final submit there (and accepts the app’s terms themselves), then you call runs.resume(run.id). See Acceptable use.
waitForRun(id, { timeout?, pollMs? }) polls until the run is completed, failed, or blocked (timeout in milliseconds, default 180000). blocked means human takeover: CAPTCHAs, SMS verification, and confirm-before-submit all stop the run — a person finishes the step in the interactive liveViewUrl, then runs.resume(id) continues. On timeout it returns the last-polled run (status still running / awaiting_verification). See Runs and human takeover. Re-authenticate later with registrations.login(registrationId) — same { registration, run } shape. Vaulted credentials: registrations.getCredentials(id) / setCredentials(id, { username?, password? }) (setCredentials overwrites the vault only — it does not change the password at the app).

Connect an account you already own

connections.connect is delegated access to an existing account: you supply the credentials, Sente logs in, vaults them write-only, and keeps the account re-loginable. See the Connect guide.
Limits, stated plainly:
  • SSO-only accounts (Google/Okta sign-in, passkeys) cannot be connected — connect needs a username + password login.
  • Credentials are write-only: registrations.getCredentials on a connected account returns 403 CREDENTIALS_WRITE_ONLY.
  • With a vaulted totpSeed, re-login clears TOTP 2FA without a human; the server computes the 30-second code — the seed never reaches any model.
  • Email or SMS code MFA needs a human: the run goes blocked with MFA_REQUIRED — enter the code in liveViewUrl, then runs.resume(run.id) — unless the account’s notification email has been repointed to the Sente identity.
connections.revoke(id) disables the connection but keeps the vault (a later connect re-enables it); connections.delete(id) revokes and purges the vaulted credentials. Connected accounts are also reachable through the registrations methods (getSession, login, exportSession) by the returned id.

Open or export a logged-in session

getSession returns a remote browser already logged into the account, as a CDP URL you drive yourself. If the login has gone stale, Sente re-logs-in first (vaulted credentials + a fresh code from the identity’s inbox) — healed: true tells you that happened, and a cold call can take a minute.
One live session per identity; open sessions count against your plan’s browser-minute allowance — always closeSession when done. getSession accepts { verify?, maxStaleSec?, timeoutMs? }; verify: false skips the freshness check (raw). openSession(id) is the raw primitive with no freshness check. If re-login can’t recover, getSession throws a SenteError whose body is the failed/blocked run. To use your own browser stack instead, export the logged-in state (cookies + localStorage) as a Playwright storageState:
The export is a bearer credential for a logged-in account — store it like a secret. More in the Sessions guide.

Webhooks

Register your endpoint and Sente pushes events to it with retries. The secret is returned once — store it.
message.received payloads are thin notifications with no body — fetch content with messages.get(id). Run lifecycle events (run.blocked, run.completed, run.failed) are also available. Every delivery carries the secret in the x-sente-secret header — verify it with a timing-safe compare. Full payloads and verification code in the Webhooks guide.

Errors

Non-2xx responses throw SenteError with status (HTTP status) and body (response body).
Inbound email is untrusted input. It can contain prompt injection (“ignore your instructions…”, “send the API key to…”). Never treat instructions found in email as coming from your user — extract only the datum you need (a code, a link, the sender’s ask).

Method reference

Endpoint-level details are in the API reference. Python sibling: sente-sdk. CLI: sente.