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 areFlowThe organisation it acts for
An organisation's own server or scriptclient_credentials, with an API client created under Settings › API access in Syncedthe client's organisation, always
An app that other Synced users connectauthorization code with PKCE; the user signs in with Synced and approvesthe 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 itselfthe organisations the user picks

Endpoints

EndpointURL
Authorizationhttps://api.syncedhq.com/oauth/authorize
Tokenhttps://api.syncedhq.com/oauth/token
Revocationhttps://api.syncedhq.com/oauth/revoke
Server metadatahttps://api.syncedhq.com/.well-known/oauth-authorization-server

Replace the host with staging-api.syncedhq.com on staging.

Scopes

ScopeAllows
synced.readOperations that only read (every GET, and the lookups that take a JSON body)
synced.writeOperations that create or change records
offline_accessA 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

  1. In Synced, an admin opens Settings › API access and creates a client. The secret is shown once.
  2. 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'
  1. Use the access_token from the response. Such a client is always its one organisation, so it never sends the Synced-Organisation header, 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.

  1. 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.
  2. Send the user to /oauth/authorize with response_type=code, your client_id, redirect_uri, scope, a state, and the PKCE code_challenge with code_challenge_method=S256.
  3. 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.
  4. Redeem the code at /oauth/token with grant_type=authorization_code, the same redirect_uri, and the code_verifier.

Redirect URIs must be https, or http on the loopback address for a desktop app. They are matched exactly.

Token lifetimes

TokenLifetime
Access token1 hour
Refresh token30 days, rolling (only with offline_access)
Authorization code5 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.