Smileline
Build an app

Connect a practice with OAuth

The authorization-code flow with PKCE against Smileline's global authorise and token URLs, and what to keep from the token response.

Smileline runs one OAuth 2.0 authorization server per region. Your app does not need to know which region a practice lives in: one global authorise URL asks the person, and one global token URL forwards the exchange to the right region.

EndpointURL
Authorisehttps://developer.smileline.io/oauth/authorize
Tokenhttps://developer.smileline.io/oauth/token
Revokehttps://developer.smileline.io/oauth/revoke

PKCE (S256) is mandatory. The grants are authorization_code and refresh_token; there is no client-credentials grant, because every connection acts as a person in a practice.

The flow

Send an owner or manager of the practice to the authorise URL with your client id, redirect URI, scopes, a state value and a PKCE challenge:

https://developer.smileline.io/oauth/authorize
  ?response_type=code
  &client_id=app_c1i2e3n4t5
  &redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth%2Fcallback
  &scope=patients.read%20appointments.write%20offline_access
  &state=8f3a…
  &code_challenge=E9Melhoa…
  &code_challenge_method=S256

The page asks which region the practice is in (UK & Europe or United States) and hands the request, unchanged, to that region's server. If you already know the region, add region=eu or region=us to skip the question.

The person signs in to Smileline, picks the practice, and reviews the consent screen, which lists exactly the areas you asked for. Unreviewed apps carry a notice there; see Testing.

Exchange the code at the token URL. The region is resolved from the code; you send the same request whichever region issued it:

curl -X POST https://developer.smileline.io/oauth/token \
  -d grant_type=authorization_code \
  -d client_id=app_c1i2e3n4t5 \
  -d client_secret=$CLIENT_SECRET \
  -d code=$CODE \
  -d code_verifier=$PKCE_VERIFIER \
  -d redirect_uri=https://app.example.com/oauth/callback

Store api_base_url, organization_id and organization_name from the response alongside the tokens. Every API request for this connection goes to api_base_url; the refresh grant does not repeat these fields.

{
	"access_token": "sl_oat_…",
	"refresh_token": "sl_ort_…",
	"token_type": "Bearer",
	"expires_in": 3600,
	"scope": "patients.read appointments.write offline_access",
	"api_base_url": "https://api-eu.smileline.io",
	"organization_id": "01991c5e-…",
	"organization_name": "Bright Smiles",
	"organization_country": "GB"
}

Using the connection

Send the access token as a Bearer token to api_base_url. Do not send X-Organization-ID: the token already names the practice.

curl "$API_BASE_URL/patients?limit=10" \
  -H "Authorization: Bearer sl_oat_…"

Access tokens last one hour; refresh tokens last 30 days and rotate on every use, so store the replacement each time. Refresh at the same global token URL with grant_type=refresh_token. A 401 means refresh; a 403 with INSUFFICIENT_SCOPE means the connection was not granted that area.

Revoking

POST https://developer.smileline.io/oauth/revoke with the token and your client credentials ends the connection. The practice can also disconnect the app from Settings → Integrations → Connected apps, which takes effect on the connection's next request and archives its webhook endpoints.

Regional endpoints

The global URLs are a front door. If you prefer to address a region directly, the servers are https://api-eu.smileline.io/auth/oauth2/* and https://api-us.smileline.io/auth/oauth2/*, with the same authorize, token and revoke paths. A code or token from one region is meaningless to the other.

On this page