Authorization with OAuth
Connect a Scalev business account with OAuth 2.0 Authorization Code and PKCE.
Overview
Scalev uses OAuth 2.0 Authorization Code with PKCE.
The flow is split across two surfaces:
- the merchant-facing authorization step starts on the Scalev frontend authorize page
- the token, introspection, revocation, and installation endpoints live on the
/v3/oauth/*API surface
In practice, the flow looks like this:
- Register your application with Scalev
- Generate PKCE parameters
- Redirect the merchant's browser to Scalev's authorize page
- The merchant signs in and approves your app for one or more businesses
- Your backend receives an authorization code
- Your backend exchanges the code for tokens
- Your backend uses the access token to call Scalev
/v3APIs, selecting one business for each business-scoped request when needed - Your backend refreshes, introspects, or revokes tokens as needed
The authorize page is a frontend route, not a machine API endpoint. It is intentionally separate from the
/v3API contract.
URLs
Frontend Authorization URL
https://app.scalev.com/oauth/authorizeAPI Base URL
https://api.scalev.comAll public machine OAuth endpoints in this guide live under:
https://api.scalev.com/v3/oauth/*Step 0: Register and Prepare Your App
Before merchants can install your app, create and configure your OAuth application in Scalev.
Important app fields include:
client_idclient_secretredirect_uri- optional
manage_url - requested scopes
- optional webhook settings
- optional billing tags
- optional IP allowlist
Important rules:
redirect_urimust match exactlyclient_secretmust stay server-side- if your app uses webhook events, configure the app webhook settings correctly
- if your app supports merchant launch links from Scalev, set
manage_url - merchant installation is blocked until the app is verified
Step 1: Generate PKCE Parameters
Before redirecting the merchant, generate:
- a
code_verifier - a
code_challengederived from that verifier using SHA-256 and Base64 URL encoding
Code Verifier Example (JavaScript)
function generateCodeVerifier(length = 100) {
if (!Number.isInteger(length) || length < 43 || length > 128) {
throw new Error("Length must be an integer between 43 and 128.");
}
const charset =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
const randomValues = crypto.getRandomValues(new Uint8Array(length));
return Array.from(
randomValues,
(byte) => charset[byte % charset.length],
).join("");
}Code Challenge Example (JavaScript)
function base64UrlEncode(buffer) {
return btoa(String.fromCharCode(...new Uint8Array(buffer)))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}
async function generateCodeChallenge(codeVerifier) {
const encoder = new TextEncoder();
const data = encoder.encode(codeVerifier);
const digest = await crypto.subtle.digest("SHA-256", data);
return base64UrlEncode(digest);
}Store the code_verifier securely. You will need it during token exchange.
Step 2: Redirect the Merchant to Scalev
Redirect the merchant's browser to:
https://app.scalev.com/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=code&state=RANDOM_STATE_STRING&code_challenge=CODE_CHALLENGE&code_challenge_method=S256Required query parameters:
| Parameter | Required | Description |
|---|---|---|
client_id | Yes | Your OAuth app client ID |
redirect_uri | Yes | Must exactly match the configured redirect URI |
response_type | Yes | Must be code |
state | Yes | Random CSRF-protection value generated by your app |
code_challenge | Yes | Base64 URL encoded SHA-256 hash of the code verifier |
code_challenge_method | Yes | Must be S256 |
Current behavior:
- PKCE is required
- only
S256is accepted - the authorize entrypoint is the frontend URL, not the
/v3API - if the merchant is not signed in, Scalev will take them through login and then return them to the authorize page with the same OAuth query parameters
Optional: Fetch Public App Metadata Before Redirect
If you want to preflight or display app metadata from your backend, call:
GET https://api.scalev.com/v3/oauth/application?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URIThis returns public app metadata such as:
client_idnamedescriptionlogo_urlhomepage_urlredirect_uri
Step 3: Merchant Reviews and Approves the App
On the Scalev authorize page, the merchant can review and approve:
- the app identity
- the businesses they want to connect
- the requested scopes
- webhook permissions, if applicable
- billing-tag approvals, if applicable
The merchant can select multiple businesses in one OAuth approval when their membership in each business allows app authorization. Scalev records access separately for each selected business. The same approved scopes, webhook settings, and billing-tag approvals apply to every selected business in the current flow.
The frontend then completes the consent flow and redirects the merchant back to your configured redirect_uri.
Step 4: Receive the Authorization Code
After approval, your backend callback receives:
https://your-app.example.com/oauth/callback?code=AUTHORIZATION_CODE&state=RANDOM_STATE_STRINGRequired validation:
- verify the returned
state - exchange the code promptly
Current authorization code behavior:
- authorization-code flow only
- code TTL is 10 minutes
- each code can be exchanged only once
Step 5: Exchange the Code for Tokens
From your backend, exchange the code at:
POST https://api.scalev.com/v3/oauth/token
Content-Type: application/json
{
"grant_type": "authorization_code",
"code": "AUTHORIZATION_CODE",
"code_verifier": "CODE_VERIFIER",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}Successful response:
{
"access_token": "ACCESS_TOKEN",
"refresh_token": "REFRESH_TOKEN",
"token_type": "Bearer",
"expires_in": 3600
}Response fields:
| Field | Description |
|---|---|
access_token | Token used for authenticated /v3 API requests |
refresh_token | Token used to obtain a new access token |
token_type | Always Bearer |
expires_in | Access-token lifetime in seconds |
Step 6: Use the Access Token
Use the access token in the Authorization header:
Authorization: Bearer ACCESS_TOKENYour backend can then call the merchant-authorized /v3 endpoints that match the granted scopes.
Token Identity and Connected Businesses
Call /v3/me after receiving an access token:
GET https://api.scalev.com/v3/me
Authorization: Bearer ACCESS_TOKENFor OAuth tokens, this returns token and user identity plus the active businesses connected to the token:
{
"auth_method": "oauth",
"user": {
"id": 123,
"unique_id": "USER123",
"email": "[email protected]",
"fullname": "Jane Doe",
"avatar": "https://..."
},
"oauth_application": {
"client_id": "client_abc",
"name": "Example App"
},
"connected_businesses": [
{
"unique_id": "ABC123",
"username": "store-a",
"name": "Store A",
"is_enabled": true,
"scopes": ["order:list", "order:read"]
}
]
}/v3/me is token and user identity. It is not selected-business identity. Revoked or inactive OAuth installations are omitted from connected_businesses[]. If no active connected business remains, Scalev returns 403 without identity data.
Select a Business for API Calls
Business-scoped /v3 API calls always run against one selected business.
- if the token has one connected business, you can omit the selector
- if the token has multiple connected businesses, pass
b_uid=<business.unique_id>on each business-scoped request - use the
unique_idfrom/v3/me.connected_businesses[] - if access to the selected business is revoked or disabled, that business returns
403while other active connected businesses remain usable
Example:
GET https://api.scalev.com/v3/orders?b_uid=ABC123
Authorization: Bearer ACCESS_TOKENUse the business unique_id returned by /v3/me as the API selector, for example b_uid=ABC123.
Useful First Checks
Token Introspection
POST https://api.scalev.com/v3/oauth/introspect
Content-Type: application/json
{
"token": "ACCESS_TOKEN",
"token_type": "access",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}Use this to check whether the token is currently active for runtime use.
Installation Status Snapshot
POST https://api.scalev.com/v3/oauth/installation/status
Content-Type: application/json
{
"token": "ACCESS_TOKEN",
"token_type": "access",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}This returns the merchant installation snapshot, including:
authorized_business_idclient_idis_activeis_enabledgranted_scopeswebhook_statusgranted_webhook_eventsapproved_billing_tagsmanage_launch_availableupdated_at
Example:
{
"authorized_business_id": 123,
"client_id": "client_abc",
"is_active": true,
"is_enabled": true,
"granted_scopes": ["order:list", "order:read"],
"webhook_status": "active",
"granted_webhook_events": ["payment.received", "shipment.status.updated"],
"approved_billing_tags": [],
"manage_launch_available": true,
"updated_at": "2026-04-01T06:10:12.345678Z"
}/v3/oauth/installation/status returns one merchant connection snapshot. If you pass a multi-business token reference, Scalev returns an error because this endpoint has no business selector. Use /v3/me to list businesses connected to a multi-business token.
Step 7: Refresh Access Tokens
When the access token expires, use the refresh token:
POST https://api.scalev.com/v3/oauth/token
Content-Type: application/json
{
"grant_type": "refresh_token",
"refresh_token": "REFRESH_TOKEN",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}Successful response:
{
"access_token": "NEW_ACCESS_TOKEN",
"refresh_token": "NEW_REFRESH_TOKEN",
"token_type": "Bearer",
"expires_in": 3600
}Important notes:
- always replace both the access token and the refresh token after a successful refresh
- refresh-token exchange is part of the
/v3/oauth/tokenflow - refresh reloads the token's connected businesses and keeps only business access that is active and enabled
- if one business is revoked or disabled, the refreshed tokens contain only the remaining businesses
- if the last connected business is revoked or disabled, refresh fails
- the old access token is not rewritten; until refresh or expiry, requests for a revoked or disabled selected business fail, but other connected businesses can still work
Token Lifecycle
Current behavior:
- access token TTL: 1 hour
- refresh token TTL: 30 days
Token Revocation
To revoke a token:
POST https://api.scalev.com/v3/oauth/revoke
Content-Type: application/json
{
"token": "TOKEN_TO_REVOKE",
"token_type": "refresh",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}Current token_type values are:
accessrefresh
Successful revocation returns:
- HTTP
204 No Content
Optional: Merchant Manage Launch Flow
If your app defines a manage_url, Scalev can launch merchants into your app with a short-lived launch_token.
Exchange that token from your backend with:
POST https://api.scalev.com/v3/oauth/installation/launch-token/exchange
Content-Type: application/json
{
"launch_token": "LAUNCH_TOKEN",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}This returns the same installation snapshot shape as /v3/oauth/installation/status.
Important notes:
- the
launch_tokenis one-time and short-lived - it is not an access token
- there is no shared manage-link secret; your backend authenticates the exchange with
client_idandclient_secret
Best Practices
- Redirect merchants to the frontend authorize page, not to a backend
/v2API endpoint. - Generate a new PKCE verifier and challenge for every install attempt.
- Validate
stateon every callback. - Keep
client_secret, access tokens, and refresh tokens on the server only. - Use only the scopes, webhook events, and billing tags your app actually needs.
- Treat
redirect_urias an exact-match value. - Use
/v3/oauth/installation/statuswhen you need the authoritative merchant connection snapshot. - Use
/v3/oauth/introspectfor runtime token diagnostics. - Handle disabled business access separately from revoked business access.
Rate Limiting
OAuth access tokens use rate limits scoped to the OAuth application and connected business.
Current default:
- 10,000 requests per hour per connected business
- 100 requests per 10 seconds per connected business for short bursts
Rate-limit responses return 429 Too Many Requests with X-Ratelimit-Limit, X-Ratelimit-Remaining, and X-Ratelimit-Reset headers. Some endpoint-specific limits are stricter, and rate-limit responses may be plain text instead of the normal JSON error shape.
Error Handling
OAuth endpoints may return errors such as:
| Error Code | Description |
|---|---|
invalid_request | Missing or malformed request parameters |
invalid_client | Client authentication failed |
invalid_grant | Authorization code, refresh token, or PKCE verifier is invalid or expired |
unauthorized_client | The client is not allowed to use the requested grant type |
server_error | Unexpected server-side failure |
Typical error response shape:
{
"error": "Error message",
"error_code": "invalid_grant"
}PKCE-related failures typically surface as:
invalid_grantwhen the code verifier does not matchinvalid_requestwhen required PKCE parameters are missing or malformed
Updated about 1 hour ago
