Skip to main content
sente-sdk is a thin Python client for the Sente REST API. Stdlib only (urllib) — no dependencies. The PyPI distribution is sente-sdk; the import is sente.

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.
Sente(api_key, base_url=...)base_url defaults to https://api.sente.run.

Create an identity

An identity is a real, reusable email address on sente.run that your agent owns. See Identities and registrations.
If the local_part is taken, create raises a 409 SenteError (on_conflict="error" is the default); pass on_conflict="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 return None on timeout. timeout is in seconds (1–60 per call, 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, sente.messages.stream(identity_id) is a 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 app_url under the identity’s email and completes email verification from the identity’s own inbox. It returns RegisterResult(registration, run).
Register only at apps whose terms permit it. confirm_before_submit=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 live_view_url — a person clicks the final submit there (and accepts the app’s terms themselves), then you call runs.resume(run.id). See Acceptable use.
wait_for_run(run_id, timeout=180, poll_ms=2000) polls until the run is completed, failed, or blocked (timeout in seconds, default 180 — note the TypeScript SDK takes milliseconds). blocked means human takeover: CAPTCHAs, SMS verification, and confirm-before-submit all stop the run — a person finishes the step in the interactive live_view_url, then runs.resume(run_id) continues. On timeout it returns the last-polled Run. See Runs and human takeover. Re-authenticate later with registrations.login(registration_id) — same RegisterResult shape. Vaulted credentials: registrations.get_credentials(registration_id) returns { "username", "password", "origin" }; set_credentials(registration_id, username=..., password=...) 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.get_credentials on a connected account raises 403 CREDENTIALS_WRITE_ONLY.
  • With a vaulted totp_seed, 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 live_view_url, then runs.resume(run.id) — unless the account’s notification email has been repointed to the Sente identity.
connections.revoke(connection_id) disables the connection but keeps the vault (a later connect re-enables it); connections.delete(connection_id) revokes and purges the vaulted credentials. Connected accounts are also reachable through the registrations methods (get_session, login, export_session) by the returned id.

Open or export a logged-in session

get_session 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 is True when 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 close_session when done. get_session accepts verify= (default True; False skips the freshness check), max_stale_sec=, and timeout_ms= (default 240000). If re-login can’t recover, it raises SenteError (inspect the body’s run). open_session(registration_id) is the raw primitive with no freshness check. To use your own browser stack instead, export the logged-in state (cookies + localStorage) as a Playwright storage_state dict:
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 only present on the Webhook returned at register time — store it.
message.received payloads are thin notifications with no body — fetch content with sente.messages.get(message_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 hmac.compare_digest. Full payloads and verification code in the Webhooks guide.

Errors

Non-2xx responses raise SenteError with status (HTTP status) and body (the parsed error payload, if any).
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. TypeScript sibling: @sente-labs/sdk. CLI: sente.