Implement the Authorization Code flow to access the ElevatedPOS API on behalf of merchant accounts. OAuth apps can act on a merchant's data with the exact permissions they approve — no need to handle merchant credentials.
Your App Auth Server Merchant (User)
│ │ │
│ 1. Redirect to /authorize │ │
│ ─────────────────────────► │ │
│ │ 2. Show consent screen │
│ │ ─────────────────────────────►│
│ │ │
│ │ 3. Merchant approves │
│ │ ◄─────────────────────────────│
│ │ │
│ 4. Redirect with ?code=… │ │
│ ◄───────────────────────── │ │
│ │ │
│ 5. POST /oauth/token │ │
│ {code, client_secret} │ │
│ ─────────────────────────► │ │
│ │ │
│ 6. {access_token, │ │
│ refresh_token} │ │
│ ◄───────────────────────── │ │
│ │ │
│ 7. API calls with Bearer │ │
│ ─────────────────────────► │ │Create an OAuth app in the developer dashboard to receive a client_id and client_secret.
Send the merchant to the ElevatedPOS authorization URL with your client_id, requested scopes, and redirect_uri.
GET https://app.elevatedpos.com.au/oauth/authorizeThe merchant reviews requested permissions on the ElevatedPOS consent screen and approves or denies.
ElevatedPOS redirects to your redirect_uri with a code parameter. Exchange it for tokens immediately — codes expire in 60 seconds.
POST https://api.elevatedpos.com.au/api/v1/oauth/tokenUse the access_token as a Bearer token. It expires in 900 seconds — refresh using refresh_token before expiry.
https://app.elevatedpos.com.au/oauth/authorize ?response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=https%3A%2F%2Fyourapp.com%2Fcallback &scope=catalog%3Aread+orders%3Aread+customers%3Awrite &state=RANDOM_CSRF_STATE_TOKEN
response_typecodeAlways "code" for Authorization Code flow.client_idYOUR_CLIENT_IDFrom your registered OAuth app.redirect_urihttps://yourapp.com/callbackMust match a registered URI exactly.scopecatalog:read orders:readSpace-separated list of requested scopes.staterandom_stringCSRF protection token. Verify on callback to prevent CSRF attacks.Node.js — exchange authorization code for tokens
async function exchangeCodeForTokens(code: string) {
const response = await fetch('https://api.elevatedpos.com.au/api/v1/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'authorization_code',
code,
client_id: process.env.ELEVATEDPOS_CLIENT_ID,
client_secret: process.env.ELEVATEDPOS_CLIENT_SECRET,
redirect_uri: 'https://yourapp.com/callback',
}),
});
if (!response.ok) {
const err = await response.json();
throw new Error(`Token exchange failed: ${err.error_description}`);
}
return response.json() as Promise<TokenResponse>;
}Node.js — refresh an expired access token
async function refreshAccessToken(refreshToken: string) {
const response = await fetch('https://api.elevatedpos.com.au/api/v1/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: process.env.ELEVATEDPOS_CLIENT_ID,
client_secret: process.env.ELEVATEDPOS_CLIENT_SECRET,
}),
});
if (!response.ok) throw new Error('Failed to refresh token — re-authorize required');
return response.json() as Promise<TokenResponse>;
}Token response format
interface TokenResponse {
access_token: string; // Bearer token for API requests
refresh_token: string; // Use to obtain a new access_token
token_type: 'Bearer';
expires_in: 900; // Seconds until access_token expires (15 min)
scope: string; // Approved scopes (may differ from requested)
org_id: string; // The merchant's organisation ID
}
// Example JSON response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 900,
"scope": "catalog:read orders:read customers:write",
"org_id": "org_01HXXXXXXXXXXXXXXXX"
}Request only the scopes your integration needs. Merchants are more likely to approve minimal permission sets.
| Scope | Access | Description |
|---|---|---|
catalog:read | Read | Read products, categories, modifiers, and price lists. |
catalog:write | Write | Create and update products, categories, and price lists. |
orders:read | Read | List and retrieve orders and line items. |
orders:write | Write | Create orders, update status, initiate refunds. |
customers:read | Read | Search and retrieve customer profiles. |
customers:write | Write | Create and update customer records. |
loyalty:read | Read | View programs, members, and point balances. |
loyalty:write | Write | Accrue and redeem loyalty points. |
payments:read | Read | View payment records and methods. |
payments:write | Write | Create payment intents, capture, and void. |
inventory:read | Read | Read stock levels, movements, and alerts. |
inventory:write | Write | Adjust stock and create transfers. |
reports:read | Read | Access sales, inventory, and financial reports. |
webhooks:write | Write | Register and manage webhook subscriptions. |
automations:read | Read | Read automation rules and execution history. |
automations:write | Write | Create and modify automation rules. |
Tokens can be revoked by the merchant (via the ElevatedPOS dashboard) or programmatically by your app:
// Revoke a token (access or refresh)
await fetch('https://api.elevatedpos.com.au/api/v1/oauth/revoke', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: accessTokenOrRefreshToken,
client_id: process.env.ELEVATEDPOS_CLIENT_ID,
client_secret: process.env.ELEVATEDPOS_CLIENT_SECRET,
}),
});
// Returns 200 OK on success (even if token was already expired)Revoking an access token immediately invalidates it. Revoking a refresh token invalidates all access tokens derived from it.
Access Token
15 minutes
Short-lived. Include as Authorization: Bearer <token> on every API request.
Refresh Token
30 days
Long-lived. Use to obtain a new access token. Rotating — each use issues a new refresh token.
Authorization Code
60 seconds
One-time use. Exchange immediately after receiving. Codes are invalidated after first use.
Keep client_secret server-side only
Never expose your client_secret in frontend JavaScript, mobile binaries, or public repositories. It should only live in server-side environment variables.
Always validate the state parameter
Generate a cryptographically random state value before redirecting. Verify it matches on callback. This prevents CSRF attacks where a malicious site tricks a user into authorizing your app.
Request minimal scopes
Request only the scopes your integration actually needs. Merchants are more likely to approve minimal permissions, and a narrower token minimises damage if compromised.
Implement PKCE for public clients
For mobile apps and SPAs where client_secret cannot be kept secret, implement PKCE (Proof Key for Code Exchange, RFC 7636). Generate a code_verifier and send its SHA256 hash as code_challenge.
Rotate refresh tokens on every use
ElevatedPOS issues a new refresh token each time you use one. Always store and use the latest refresh token. If a previous refresh token is presented, ElevatedPOS will revoke the entire token family.
Store tokens encrypted at rest
Encrypt stored access and refresh tokens using AES-256-GCM or a KMS-managed key. Never store tokens in plaintext in your database.
Handle merchant-initiated revocation gracefully
Merchants can disconnect your app at any time from their dashboard. Handle 401 responses from the API by detecting invalid_grant errors and re-initiating the OAuth flow.