OAuth 2.0 for apps: authorization, scopes, and refresh tokens
LaunchMyStore apps use the OAuth 2.0 authorization code flow to gain scoped access to a merchant's store. This guide walks you through the full handshake: building the authorize URL, handling the redirect, exchanging the code for tokens, and refreshing access before it expires.
The overall flow
- Merchant clicks Install on your app's listing.
- Their admin redirects to your authorize URL with the app's
client_id, requestedscope, yourredirect_uri, and a CSRFstate. - Merchant approves the scopes; LaunchMyStore redirects back to your
redirect_uriwith a one-timecodeand the samestate. - Your server exchanges
codefor an access token (24h lifetime) and a refresh token (30 day lifetime). - Your server uses the access token to call
/api/v1/*endpoints. When it nears expiry, exchange the refresh token for a fresh pair.
Authorization codes themselves are short-lived (10 minutes) and one-time use — consume them immediately.
Step 1: Build the authorize URL
https://api.launchmystore.io/apps/oauth/authorize?
client_id=YOUR_API_KEY&
scope=read_products,write_products,read_orders&
redirect_uri=https://app.example.com/oauth/callback&
state=RANDOM_CSRF_TOKEN&
shop=MERCHANT_STORE_HANDLE
Always generate a fresh, unguessable state per install (e.g. crypto.randomBytes(32).toString('hex')) and store it server-side keyed by session. Verify it matches when the merchant returns — this prevents CSRF.
Step 2: Handle the redirect
The merchant lands on your redirect_uri with:
https://app.example.com/oauth/callback?
code=ONE_TIME_CODE&
state=RANDOM_CSRF_TOKEN&
shop=merchant-store
Verify state matches what you stored. If not, abort — you're being attacked.
Step 3: Exchange code for tokens
POST https://api.launchmystore.io/apps/oauth/token
Content-Type: application/json
{
"grant_type": "authorization_code",
"client_id": "YOUR_API_KEY",
"client_secret": "YOUR_CLIENT_SECRET",
"code": "ONE_TIME_CODE",
"redirect_uri": "https://app.example.com/oauth/callback"
}
Successful response:
{
"access_token": "lms_at_...",
"token_type": "Bearer",
"expires_in": 86400,
"refresh_token": "lms_rt_...",
"scope": "read_products,write_products,read_orders"
}
Store both tokens, keyed by the merchant’s store handle (or merchant id). Store securely — they grant the same access as a logged-in merchant within the requested scopes.
Step 4: Call the API
fetch('https://api.launchmystore.io/api/v1/products', {
headers: { Authorization: 'Bearer lms_at_...' },
});
All app-scoped endpoints live under /api/v1/. They enforce the scope you were granted and apply tier-based rate limiting (FREE 20 req/s, BASIC 40, PRO 100, ENTERPRISE 500) via a sliding window per app + store.
Step 5: Refresh before expiry
Access tokens last 24h. Before they expire (or on the first 401), exchange the refresh token:
POST https://api.launchmystore.io/apps/oauth/token
Content-Type: application/json
{
"grant_type": "refresh_token",
"client_id": "YOUR_API_KEY",
"client_secret": "YOUR_CLIENT_SECRET",
"refresh_token": "lms_rt_..."
}
You'll receive a fresh access token and a fresh refresh token. Discard the old pair. The refresh window resets to 30 days from the new exchange, so an active app effectively stays installed forever.
Scopes catalog
Request only what you need — reviewers reject apps that ask for surplus scope, and merchants see the full list at install time. Common scopes:
read_products,write_productsread_orders,write_ordersread_customers,write_customersread_metafields,write_metafields— required for both definitions and values.read_inventory,write_inventoryread_themes,write_themesread_discounts,write_discountsread_analytics
The full list lives in the developer documentation. Read and write are separate scopes — a write scope does not imply its matching read scope. If your app both reads and writes a resource, request both (for example, read_products and write_products). Calling a read endpoint with only the write scope returns 403 Forbidden.
Webhooks and uninstall
The host fires an app/uninstalled webhook (one of 31 supported topics) when a merchant removes your app. Treat it as authoritative — the access token is revoked immediately. Webhooks have 3 retries with exponential backoff (1m / 5m / 15m), so reply 200 as soon as you've queued the work.
FAQ
What scopes does my app need to read metafields?
read_metafields for read access to both definitions and values; add write_metafields if you need to create or update either. Scopes are shared between definitions and value endpoints — one pair covers both.
How long does the install code stay valid?
10 minutes, and it's one-time-use. The state expires on the same clock. Always exchange immediately on receipt — don't queue it.
Can I have more than one redirect URI?
Yes. List every variant in your app configuration (production, staging, localhost). The token endpoint requires the redirect_uri to exactly match one of the registered ones — trailing slashes count.
What happens if my refresh token expires?
After 30 days of inactivity, the refresh token expires and you'll get an invalid_grant error. You'll need the merchant to reinstall (re-run the authorize flow). Plan to refresh well before the 30-day window if your app is dormant for long stretches.
How do I revoke a token?
Call the revoke endpoint:
POST https://api.launchmystore.io/apps/oauth/revoke
Content-Type: application/json
{
"token": "lms_at_..."
}
Revoking the access token also revokes the paired refresh token, so the app loses access immediately. The endpoint is idempotent — revoking an already-revoked or unknown token still returns success. Tokens are also revoked automatically when the merchant uninstalls your app. To invalidate every token your app has ever issued at once, rotate the client_secret from the developer portal.
Why am I getting 429 errors?
You're exceeding your tier's rate limit on /api/v1/. Each app+store pair has a per-second sliding window: FREE 20, BASIC 40, PRO 100, ENTERPRISE 500. Back off and retry; or upgrade your tier in the developer portal.