Authentication
OAuth 2.0: which flow to use, the scopes, and how long tokens last.
The API is OAuth 2.0. Every request carries an access token in the Authorization: Bearer header. Which flow you use depends on who is calling.
| You are | Flow | The organisation it acts for |
|---|---|---|
| An organisation's own server or script | client_credentials, with an API client created under Settings › API access in Synced | the client's organisation, always |
| An app that other Synced users connect | authorization code with PKCE; the user signs in with Synced and approves | the organisations the user picks on the consent screen |
| An AI assistant over MCP (Claude, ChatGPT, VS Code…) | the same authorization code flow; the client discovers and registers itself | the organisations the user picks |
Endpoints
| Endpoint | URL |
|---|---|
| Authorization | https://api.syncedhq.com/oauth/authorize |
| Token | https://api.syncedhq.com/oauth/token |
| Revocation | https://api.syncedhq.com/oauth/revoke |
| Server metadata | https://api.syncedhq.com/.well-known/oauth-authorization-server |
Replace the host with staging-api.syncedhq.com on staging.
Scopes
| Scope | Allows |
|---|---|
synced.read | Operations that only read (every GET, and the lookups that take a JSON body) |
synced.write | Operations that create or change records |
offline_access | A refresh token, for apps that keep working between sign-ins |
A user can leave write unticked on the consent screen, and the token then has read access only. Each operation's page says which scope it needs.
An organisation's own client
- In Synced, an admin opens Settings › API access and creates a client. The secret is shown once.
- Exchange the client id and secret for a token:
curl -X POST 'https://api.syncedhq.com/oauth/token' \
-d 'grant_type=client_credentials' \
-d 'client_id=org_xxxxxxxxxxxx' \
-d 'client_secret=YOUR_SECRET' \
-d 'scope=synced.read synced.write'
- Use the
access_tokenfrom the response. Such a client is always its one organisation, so it never sends theSynced-Organisationheader, and roles do not apply to it: only an admin can create one.
Keep the secret on your server. Never ship it in a browser, a mobile app or a spreadsheet.
An app other people connect
A public client (one that cannot keep a secret: a browser app, a desktop app, an assistant) uses authorization code with PKCE. PKCE is required, S256 only.
- Register the app. A Synced admin can register it under Admin › Integrations › API clients, which marks it as verified on the consent screen. An app can also register itself with
POST /oauth/register(RFC 7591); it is then shown as unverified until an admin registers it. - Send the user to
/oauth/authorizewithresponse_type=code, yourclient_id,redirect_uri,scope, astate, and the PKCEcode_challengewithcode_challenge_method=S256. - The user signs in to Synced (password or Continue with Xero) and picks the organisations the app may use. If they belong to only one, they are not asked.
- Redeem the code at
/oauth/tokenwithgrant_type=authorization_code, the sameredirect_uri, and thecode_verifier.
Redirect URIs must be https, or http on the loopback address for a desktop app. They are matched exactly.
Token lifetimes
| Token | Lifetime |
|---|---|
| Access token | 1 hour |
| Refresh token | 30 days, rolling (only with offline_access) |
| Authorization code | 5 minutes |
Every token is looked up on use. Revoking a client, a consent (Settings › Connected apps) or a token takes effect at once, and a user removed from an organisation gets 403 on their next call for it.
How the console on these pages signs in
The console is itself a public client of the API (synced-docs): PKCE, no secret, no refresh token. Its access token is held in your browser's memory for this tab, never stored and never sent to this website's servers. Requests go from your browser to the API directly. Reload the page and you are signed out; the token expires within an hour either way.
