DashboardDeveloper Portal

OAuth 2.0

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.

Register an app

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.

Authorization Code Flow

  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  │                               │
     │ ─────────────────────────► │                               │
1

Register your OAuth app

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.

2

Redirect the merchant

Send the merchant to the Fly authorization URL with your client_id, requested scopes, and redirect_uri.

GET https://useflyconnect.com/oauth/authorize
3

Merchant approves

The merchant reviews requested permissions on the Fly consent screen and approves or denies.

4

Exchange the code

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/token
5

Access the API

Use the access_token as a Bearer token. It expires in 900 seconds — refresh using refresh_token before expiry.

Authorization URL

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.

Token Exchange

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"
}

Scopes Reference

Request only the scopes your integration needs. Merchants are more likely to approve minimal permission sets.

ScopeAccessDescription
catalog:readReadRead products, categories, modifiers, and price lists.
catalog:writeWriteCreate and update products, categories, and price lists.
orders:readReadList and retrieve orders and line items.
orders:writeWriteCreate orders, update status, initiate refunds.
customers:readReadSearch and retrieve customer profiles.
customers:writeWriteCreate and update customer records.
loyalty:readReadView programs, members, and point balances.
loyalty:writeWriteAccrue and redeem loyalty points.
payments:readReadView payment records and methods.
payments:writeWriteCreate payment intents, capture, and void.
inventory:readReadRead stock levels, movements, and alerts.
inventory:writeWriteAdjust stock and create transfers.
reports:readReadAccess sales, inventory, and financial reports.
webhooks:writeWriteRegister and manage webhook subscriptions.
automations:readReadRead automation rules and execution history.
automations:writeWriteCreate and modify automation rules.

Token Revocation

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.

Token Lifecycle

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.

Security Best Practices

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.