| Surface | Base URL | Authentication | What it is for |
|---|---|---|---|
| Managed Users API | https://api.synci.io/managed/v1 | HTTP Basic (client_id:client_secret) | Managing your users: register, list, update, delete, mint tokens, mint portal links |
| Data API | https://api.synci.io/api/v1 | Bearer token for one user | Reading and managing that user's financial data |
oac_). Both are used as HTTP Basic credentials on the Managed Users API. Store the secret on your server; it is never safe in a browser or a mobile binary.profile:read · accounts:read · accounts:write · transactions:read · transactions:write · sensitive:read · financial-connections:read · financial-connections:write| Response | Meaning |
|---|---|
401 invalid_client | Bad or missing credentials, or the app is not a Managed app. Deliberately indistinguishable, so this endpoint cannot be used to discover which client IDs exist. |
403 client_suspended | The app has been suspended by Synci. |
error and message keys.| Field | Rules |
|---|---|
email | Required. Valid address, max 255. |
external_user_id | Optional, max 255. Your own identifier for this person, and the easiest way to reconcile later. |
name | Optional, max 255. Defaults to a title-cased version of the email local part. |
connection_limit | Optional integer, 0 to 20. A per-user cap on financial connections. |
201 Created returns the user and their first token pair, unwrapped at the top level:{
"user_id": "9f1c2e64-1f3a-4c77-9b21-8a4c5d6e7f80",
"external_user_id": "acct-9001",
"email": "alice@example.com",
"email_verified": false,
"onboarded_at": null,
"limits": {
"financial_connections": null
},
// caps ALL connection types, not just bank
"created_at": "2026-08-28T09:12:00+00:00",
"token_type": "Bearer",
"expires_in": 3600,
"access_token": "eyJ0eXAiOi...",
"refresh_token": "def50200..."
}user_id is an opaque UUID with no relationship to Synci's internal identifiers, and it is scoped to your app. It is the id you use everywhere else. Asking for another developer's user returns 404, identical to asking for one that does not exist.| Status | error | When |
|---|---|---|
409 | email_already_registered | You already registered that address. Uniqueness is per app: another developer can register the same person, and an address belonging to a Synci account can still be registered. |
409 | external_user_id_taken | You already used that external_user_id. |
403 | testing_limit_reached | An unreviewed app hit the registration cap. The response includes a limit key. Counted across all your unreviewed apps, not per app. |
402 | subscription_required | Your app is live but you have no Synci plan. Your users' connections draw on your plan's connection quantity, so there is nothing for them to draw on. |
{
"data": {
"url": "https://app.synci.io/portal/session?token=...&app_client_id=...",
"expires_at": "2026-08-28T09:22:00+00:00"
}
}url. They arrive already signed in, with no password, no code and no Synci account.return_url is optional. Provide it to override the app default for this one visit, which is useful when you know which of your screens they left from. Omit it to use the app's configured portal_return_url.403; another user's data returns 404.sensitive:read. This is enforced server-side and cannot be switched off by the caller.user_id that is not one of your users returns 400 with "error": "invalid_request"; the specific reason is in the hint field, not error_description.email_verified flips to true when someone redeems a code, and only then. A link you minted never sets it: you vouching for an address is not evidence that the person controls that mailbox, and the field would be worthless if it said otherwise. Read it as "has proven they can read this inbox", not as "we checked the address is valid".| Endpoint | What it does |
|---|---|
GET /managed/v1/users | List your users. Filter by external_user_id, email, per_page (default 50, max 200). |
GET /managed/v1/users/{user_id} | One user, including connections_count (all connection types). |
PUT or PATCH /managed/v1/users/{user_id} | Update external_user_id or connection_limit. Email cannot be changed. |
DELETE /managed/v1/users/{user_id} | 204. Hard-deletes them, cascades their connections, accounts and transactions, revokes every token, and frees their email address for re-registration. |
provider filter is how you narrow to one kind.GET /managed/v1/connectionsuser_id, external_user_id, status, provider, expiring_within_days, per_page.expiring_within_days is the one to build on. Under PSD2 a bank consent lapses every 90 to 180 days, so a nightly ?expiring_within_days=14 sweep tells you exactly who needs to re-authorize before anything breaks.| Setting | Default | What it does |
|---|---|---|
| Portal return URL | none | Where "Continue to ‹App›" sends a user who has finished in the portal. No button is shown if unset. |
| Providers | all we allow | Which bank-data providers your users may connect through: today GoCardless, Akahu and SnapTrade. A value can only narrow the set, never widen it. |
| Countries | all | Which countries your users may connect banks in (ISO-3166-1 alpha-2). |
| Default connection limit | Synci default | Bank connections per user. A per-user connection_limit overrides it. |
| Email your users | on | Whether Synci emails your users about expiring, failing and disabled connections. |
| Transaction enrichment | off | Merchant name, logo and category on transactions. Requires a Pro plan; no per-transaction charge. |
| History depth | provider default | How much transaction history a new connection requests, capped per institution. |
| Data retention | 365 days | How long Synci keeps your users' transaction data. |
GET /managed/v1/connections?expiring_within_days=... and building your own re-consent prompt. Left on, your users get the mail, it names your app, and the link drops them straight into the portal to fix it.code | Meaning |
|---|---|
no_subscription | You have no active plan. Not "full", but never started. |
trial_limit_reached | You are on a free trial; unreviewed apps share a small allowance. |
capacity_reached | You have used the connections you subscribed for. Raise your plan quantity. |
| State | What it permits |
|---|---|
| Testing | Real banks, real data, capped: a small number of registered users and, while you are on a free trial, 2 live connections across all your unreviewed apps. Counted per developer account, because creating another app is free. |
| Pending review | Under review by Synci. |
| Approved | Caps lifted; the portal stops showing your users an "unreviewed" notice and starts showing your logo. |
| Rejected | Edit and resubmit; a reason is given. |
developer_name, homepage_url and privacy_policy_url set, and an active Synci plan. Paying lifts the trial cap immediately, reviewed or not: you bought the slots.| Status | When |
|---|---|
401 | Bad Basic credentials on the Managed Users API, or an expired or revoked Bearer token on the Data API. |
402 | No active Synci plan. Subscribe before registering users. |
403 | Connection capacity or trial ceiling reached, app suspended, or a scope your app does not hold. |
404 | The user or resource does not exist, or is not yours. |
409 | Email or external_user_id already used by your app. |
422 | Validation failed; the body lists per-field errors. |
429 | Rate limit exceeded. |
user_id. Both are safe to quote.