API reference
The connect flow
Your user (a member) connects a provider account once, on Latido's hosted page — like Plaid Link. Your backend never handles provider OAuth: it creates a session, sends the user to its URL and reads the outcome from the redirect.
your backend ──POST /v1/connect/sessions──▶ Latido → { url, token, expiresAt, memberId }
your app ──redirect──▶ app.getlatido.com/connect/{token} hosted page: pick a provider
hosted page ──▶ provider consent screen ──▶ api.getlatido.com/connect/callback/{provider}
Latido ──302──▶ your redirectUrl?status=connected&provider=…&connectionId=…1. Create the session
POST /v1/connect/sessions (API key) — latido.connect.sessions.create(input)
| Field | Required | Notes |
|---|---|---|
externalUserId |
yes | Your id for this person (1-200 chars). Unique per project and mode; avoid emails. The member is created if new. |
redirectUrl |
yes | http(s) URL the member lands on afterwards. Its origin must be one of the project's allowed redirect origins (dashboard → project settings). Test keys also accept http://localhost, 127.0.0.1 and [::1] on any port. |
providers |
no | 1-20 ProviderSlugs to offer. Default: every provider the project can connect in this mode. Each one must be connectable, or the call fails. |
metadata |
no | Free JSON (≤ 2 KB) saved on the member when it is created. |
{
"data": {
"token": "cst_…",
"url": "https://app.getlatido.com/connect/cst_…",
"expiresAt": "2026-10-27T11:00:00.000Z",
"memberId": "mem_V1StGXR8_Z5jdHi6B"
}
}- The link is valid for one hour. Give the user
url, never the token alone. - Calling again for the same
externalUserIdmakes a new link for the same member; older links stay valid until they expire. - Which providers are connectable:
GET /v1/providers(available: true). In test mode that is onlylatido_demo, which needs no setup. In live mode a provider needs an enabled provider app in the project (Latido's managed app where the provider allows it, or your own client id and secret) and an adapter in this Latido build. Apple Health and Health Connect are never offered here: they connect through the mobile SDK (mobile-ingest.md).
Errors at this step: REDIRECT_URL_NOT_ALLOWED (400), PROVIDER_APP_NOT_ALLOWED (400 — a
requested provider is not connectable in this mode, or the project has none yet),
UNPROCESSABLE_CONTENT (422 — the body failed validation).
2. The hosted page
https://app.getlatido.com/connect/{token} shows your project's name and logo (logoUrl in project
settings), the offered providers with the member's current status for each, and a cancel
link. It talks to three public, token-scoped endpoints you normally never call yourself:
| Method | Path | Notes |
|---|---|---|
| GET | /connect/sessions/{token} |
What the page renders (no secrets, no other members). 410 CONNECT_SESSION_EXPIRED once expired or unknown. |
| POST | /connect/sessions/{token}/providers/{provider}/start |
{ data: { url } }: the provider's consent URL; the browser navigates there. 400 PROVIDER_NOT_OFFERED for a provider outside the session. |
| GET | /connect/callback/{provider} |
Where providers send the member back; always answers with a 302. |
Starting an authorization puts the member's connection in pending until they come back.
For the demo provider the "consent screen" is Latido's own page
(https://app.getlatido.com/connect/demo-consent): Allow succeeds, Deny comes back as
access_denied.
3. The redirect
Latido always sends the browser somewhere. With a session, it is your redirectUrl with
query parameters appended (existing ones are kept):
| Outcome | Query |
|---|---|
| Connected | status=connected&provider=<slug>&connectionId=conn_… |
| Failed | status=error&provider=<slug>&reason=<reason> |
| Member pressed cancel | status=cancelled |
reason values sent by the backend:
| reason | Meaning |
|---|---|
access_denied |
The member refused consent at the provider (or the provider reported the grant as revoked). |
provider_error |
The code exchange with the provider failed. The connection's lastError has the detail. |
provider_unavailable |
The project's provider app for it is missing, disabled or lacks credentials. |
plan_limit_reached |
Live mode: the account is at its connected-members limit. |
session_expired |
The session, member or connection vanished between start and callback. |
Parse it with the contract's schema:
const result = latido.connect.parseRedirect(request.url); // or parseConnectRedirect(url)
switch (result.status) {
case "connected": await saveConnection(result.connectionId, result.provider); break;
case "error": showError(result.reason); break;
case "cancelled": break;
}When there is no session to read a redirect URL from (an invalid or replayed state, an
unexpected failure), the member lands on Latido's own page instead:
https://app.getlatido.com/connect/error?reason=invalid_state | session_expired | unexpected_error.
Each callback state works once: replaying a callback URL ends on that page.
Do not trust the redirect alone for anything important — it travels through the browser.
Confirm with GET /v1/connections/{connectionId} or the connection.created webhook.
4. After connecting
- The connection turns
active,connection.createdis sent (connection.updatedwhen an existing connection is re-authorized), and a backfill job fetches the lastdefaultBackfillDays(project setting, default 30, capped by each provider'smaxBackfillDays).backfill.completedfollows with per-type counts; a partial failure sendsconnection.errorinstead. - New data then arrives by provider push or polling, each change raising
*.created/*.updatedevents (webhooks.md). - Statuses (
ConnectionStatus):pending,active,expired(the provider refused the tokens — send the member through a new session),revoked(by you or by the member at the provider; tokens wiped),error(the last authorization attempt failed).
Managing a connection:
await latido.connections.get(connectionId);
await latido.connections.sync(connectionId, { days: 14 }); // re-fetch; once per 5 minutes
await latido.connections.revoke(connectionId); // records stay
await latido.members.delete(memberId); // GDPR: connections + all data