Implement the Authorization Code flow to access the Fly 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.
Register an OAuth app — self-serveGA
Get a client_id and one-time client_secret in seconds. Then connect your Fly account (/oauth/start) to drive the read-write sandbox.
Requested sandbox scopes: catalog:read orders:read customers:read inventory:read loyalty:read payments:read reports:read. The full read + write scope catalog is in the Scopes Reference below.
Examples on this page use this portal's real client_id (elevatedpos-developer-portal) and redirect_uri so you can copy them verbatim. Token values are still illustrative — head to the read-write sandbox to call the API against your own seeded test org.
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 │ │
│ ─────────────────────────► │ │Register an app self-serve on the My apps page — pick a name, redirect URIs, and scopes, and get your client_id + a one-time client_secret instantly. No email, no waiting.
Send the merchant to the Fly authorization URL with your client_id, requested scopes, and redirect_uri.
GET https://useflyconnect.com/oauth/authorizeThe merchant reviews requested permissions on the Fly consent screen and approves or denies.
Fly redirects to your redirect_uri with a code parameter. Exchange it for tokens promptly — codes are single-use and expire after 10 minutes.
POST https://api.useflyconnect.com/api/v1/oauth/tokenUse the access_token as a Bearer token. It expires in 900 seconds — refresh using refresh_token before expiry.
https://useflyconnect.com/oauth/authorize ?response_type=code &client_id=elevatedpos-developer-portal &redirect_uri=https%3A%2F%2Fdeveloper.useflyconnect.com%2Foauth%2Fcallback &scope=catalog%3Aread%20orders%3Aread%20customers%3Aread%20inventory%3Aread%20loyalty%3Aread%20payments%3Aread%20reports%3Aread &state=RANDOM_CSRF_STATE_TOKEN
response_typecodeAlways "code" for Authorization Code flow.client_idelevatedpos-developer-portalThis portal's registered client_id.redirect_urihttps://developer.useflyconnect.com/oauth/callbackMust match a registered URI exactly.scopecatalog:read orders:read customers:read inventory:read loyalty:read payments:read reports: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.useflyconnect.com/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://developer.useflyconnect.com/oauth/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.useflyconnect.com/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; // Opaque 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)
}
// Example JSON response (tokens are opaque 64-char hex strings — not JWTs):
{
"access_token": "8f14e45fceea167a5a36dedd4bea2543a3d2e5f1c0b7a89e6d4c2b1a0f9e8d7c",
"refresh_token": "c7b6a5d4e3f2019283746556473829100192837465564738291001928374655a",
"token_type": "Bearer",
"expires_in": 900,
"scope": "catalog:read orders:read customers:read"
}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 Fly dashboard) or programmatically by your app:
// Revoke a token (access or refresh)
await fetch('https://api.useflyconnect.com/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
Until used or revoked
Long-lived. Use to obtain a new access token. Rotating — each use revokes it and issues a new refresh token, so always store the latest one.
Authorization Code
10 minutes
One-time use. Exchange promptly 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.
Use confidential (server-side) clients only
Fly currently issues confidential clients that authenticate with a client_secret. PKCE (RFC 7636) for public clients such as SPAs and mobile apps is not yet supported — keep the token exchange on your server.
Rotate refresh tokens on every use
Fly issues a new refresh token each time you use one and revokes the old one immediately. Always store and use the latest refresh token — presenting a previously used refresh token returns invalid_grant.
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.