Skip to main content

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:
  1. Register your application with Scalev
  2. Generate PKCE parameters
  3. Redirect the merchant’s browser to Scalev’s authorize page
  4. The merchant signs in and approves your app
  5. Your backend receives an authorization code
  6. Your backend exchanges the code for tokens
  7. Your backend uses the access token to call Scalev /v3 APIs
  8. 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 /v3 API contract.

URLs

Frontend Authorization URL

https://app.scalev.com/oauth/authorize

API Base URL

https://api.scalev.com
All 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_id
  • client_secret
  • redirect_uri
  • optional manage_url
  • requested scopes
  • optional webhook settings
  • optional billing tags
  • optional IP allowlist
Important rules:
  • redirect_uri must match exactly
  • client_secret must 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_challenge derived 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=S256
Required query parameters:
ParameterRequiredDescription
client_idYesYour OAuth app client ID
redirect_uriYesMust exactly match the configured redirect URI
response_typeYesMust be code
stateYesRandom CSRF-protection value generated by your app
code_challengeYesBase64 URL encoded SHA-256 hash of the code verifier
code_challenge_methodYesMust be S256
Current behavior:
  • PKCE is required
  • only S256 is accepted
  • the authorize entrypoint is the frontend URL, not the /v3 API
  • 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_URI
This returns public app metadata such as:
  • client_id
  • name
  • description
  • logo_url
  • homepage_url
  • redirect_uri

Step 3: Merchant Reviews and Approves the App

On the Scalev authorize page, the merchant can review and approve:
  • the app identity
  • the requested scopes
  • webhook permissions, if applicable
  • billing-tag approvals, if applicable
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_STRING
Required validation:
  1. verify the returned state
  2. 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:
FieldDescription
access_tokenToken used for authenticated /v3 API requests
refresh_tokenToken used to obtain a new access token
token_typeAlways Bearer
expires_inAccess-token lifetime in seconds

Step 6: Use the Access Token

Use the access token in the Authorization header:
Authorization: Bearer ACCESS_TOKEN
Your backend can then call the merchant-authorized /v3 endpoints that match the granted scopes.

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_id
  • client_id
  • is_active
  • is_enabled
  • granted_scopes
  • webhook_status
  • granted_webhook_events
  • approved_billing_tags
  • manage_launch_available
  • updated_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"
}

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/token flow
  • if the installation is disabled or revoked, refresh may fail

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:
  • access
  • refresh
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_token is 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_id and client_secret

Best Practices

  1. Redirect merchants to the frontend authorize page, not to a backend /v2 API endpoint.
  2. Generate a new PKCE verifier and challenge for every install attempt.
  3. Validate state on every callback.
  4. Keep client_secret, access tokens, and refresh tokens on the server only.
  5. Use only the scopes, webhook events, and billing tags your app actually needs.
  6. Treat redirect_uri as an exact-match value.
  7. Use /v3/oauth/installation/status when you need the authoritative merchant install snapshot.
  8. Use /v3/oauth/introspect for runtime token diagnostics.
  9. Handle disabled installations separately from revoked installations.

Error Handling

OAuth endpoints may return errors such as:
Error CodeDescription
invalid_requestMissing or malformed request parameters
invalid_clientClient authentication failed
invalid_grantAuthorization code, refresh token, or PKCE verifier is invalid or expired
unauthorized_clientThe client is not allowed to use the requested grant type
server_errorUnexpected server-side failure
Typical error response shape:
{
  "error": "Error message",
  "error_code": "invalid_grant"
}
PKCE-related failures typically surface as:
  • invalid_grant when the code verifier does not match
  • invalid_request when required PKCE parameters are missing or malformed