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.
| Endpoint | URL |
|---|---|
| Authorise | https://developer.smileline.io/oauth/authorize |
| Token | https://developer.smileline.io/oauth/token |
| Revoke | https://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=S256The 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/callbackStore 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.