Authentication
OAuth 2.0
Connect a merchant store with Authorization Code, PKCE S256 and exact scopes.
Use OAuth when a merchant installs your app. The merchant chooses a store and approves the exact scopes; your backend receives tokens for only that installation.
The flow
Developer App
↓ Merchant opens Install
SaaS OAuth Consent
↓ Merchant selects Store and approves Scopes
Authorization Code
↓ exact redirect URI + state
Developer Backend
↓ code + PKCE verifier + client credentials
Token Exchange
↓
Access Token + Refresh Token
↓
SaaS APIBefore you start
Create an app and version in Developer Dashboard → Apps. Register the exact callback URI, choose scopes and save the one-time Client Secret on your backend. Generate a fresh unpredictable state and PKCE verifier for every install.
Authorization request
Send the merchant's browser to:
GET https://getsas.xyz/api/oauth/authorize| Parameter | Required value |
|---|---|
client_id | App ID from Developer Dashboard |
response_type | code |
redirect_uri | Exact URI registered on the published version |
scope | Space-separated subset of reviewed scopes |
code_challenge | Base64url SHA-256 of the verifier, without padding |
code_challenge_method | S256 |
state | Fresh one-use request binding, up to 512 characters |
The merchant signs in, selects an eligible store and approves or declines. Viewing the page grants nothing.
Exchange the code
Call the token endpoint from your backend. Never exchange tokens in browser JavaScript.
curl --request POST 'https://getsas.xyz/api/oauth/token' \
--header 'Content-Type: application/json' \
--data '{
"grant_type": "authorization_code",
"client_id": "<CLIENT_ID>",
"client_secret": "<CLIENT_SECRET>",
"code": "<AUTHORIZATION_CODE>",
"redirect_uri": "https://app.example.com/oauth/callback",
"code_verifier": "<PKCE_VERIFIER>"
}'Validate state before exchange. A code is one-use and expires quickly. The redirect URI and verifier must exactly match the authorization request.
Tokens, refresh and rotation
- Access tokens last 15 minutes.
- Rotating refresh tokens last 30 days.
- Store tokens encrypted on your server and redact them from logs.
- Refresh-token reuse revokes the token family.
- Rotating the Client Secret keeps the previous secret valid for 24 hours (see below).
- Uninstall, suspension or revoked merchant access invalidates derived authority.
Refresh with grant_type=refresh_token, client_id, client_secret and the latest refresh_token. Persist the new refresh token atomically before discarding the old one.
Revoking tokens
When a merchant disconnects your integration, or you no longer need a token, revoke it from your backend (RFC 7009):
POST https://getsas.xyz/api/oauth/revoke
Content-Type: application/x-www-form-urlencoded| Parameter | Required | Value |
|---|---|---|
token | Yes | The access token or refresh token to revoke |
token_type_hint | No | access_token or refresh_token; other values are ignored |
client_id | Yes, unless sent with HTTP Basic | App ID from Developer Dashboard |
client_secret | Yes, unless sent with HTTP Basic | Current (or grace-period) Client Secret |
Authenticate the client with one method: either HTTP Basic (Authorization: Basic base64(urlencode(client_id):urlencode(client_secret))) or client_id and client_secret in the body. Sending a different client_id in the body than in the Basic header is rejected with invalid_request.
curl --request POST 'https://getsas.xyz/api/oauth/revoke' \
--user '<CLIENT_ID>:<CLIENT_SECRET>' \
--data-urlencode 'token=<REFRESH_TOKEN>' \
--data-urlencode 'token_type_hint=refresh_token'- Every valid request answers
200with{}, even when the token is unknown, already revoked or belongs to another app. Do not use the response to check whether a token exists. - Revoking a refresh token revokes its whole token family, so later refreshes fail.
- Invalid client credentials answer
401withinvalid_client; a missing or oversizedtokenanswers400withinvalid_request. - If you get
503withtemporarily_unavailable, retry later with backoff.
Rotating the client secret
Rotate the Client Secret from the Credentials page of your app in the Developer Dashboard when it may have leaked or on a regular schedule.
- Rotate the secret and copy the new secret. It is shown once.
- Update every server that calls the token or revocation endpoint with the new secret before the grace period ends.
- The previous secret stays valid for 24 hours so running servers keep working during the rollout.
- After all servers use the new secret, end the grace period early to invalidate the previous secret immediately. Do this at once if the old secret was exposed.
Store the secret only on your backend and never log it.
Apps without a client secret
Apps created before client secrets existed could exchange codes with PKCE alone. These apps now run in an explicit compatibility window shown on the Credentials page; the developer dashboard notifies their owners. Issue a Client Secret before the window ends: from then on every token and revocation request must authenticate with it. PKCE and exact redirect URIs stay required in every mode.
Rate limits
The token and revocation endpoints are limited per source IP and per client_id and IP. A refused request answers 429 with rate_limited and a Retry-After header in seconds; wait that long before retrying. Refresh tokens ahead of expiry and avoid refreshing the same installation from several workers at once.
Common errors
| Error | What to fix |
|---|---|
invalid_client | Use the correct App ID and current Client Secret. |
temporarily_unavailable | Retry the request later with backoff. |
rate_limited | Wait for the Retry-After seconds, then retry. |
invalid_grant | Restart the install; the code/verifier is invalid, expired or already used. |
invalid_scope | Request only scopes on the reviewed published version. |
redirect_uri_mismatch | Use the exact registered redirect URI. |
access_denied | The merchant declined or no longer has authority for the store. |