Authorize
curl --request GET \
--url https://api.launchmystore.io/apps/oauth/authorize \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.launchmystore.io/apps/oauth/authorize"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.launchmystore.io/apps/oauth/authorize', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.launchmystore.io/apps/oauth/authorize",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.launchmystore.io/apps/oauth/authorize"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.launchmystore.io/apps/oauth/authorize")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.launchmystore.io/apps/oauth/authorize")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"status": 123,
"state": "<string>",
"data": {
"code": "<string>",
"state": "<string>",
"redirectUri": "<string>",
"app": {}
}
}OAuth
Authorize
Start the OAuth 2.0 authorization code flow for a merchant
GET
/
apps
/
oauth
/
authorize
Authorize
curl --request GET \
--url https://api.launchmystore.io/apps/oauth/authorize \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.launchmystore.io/apps/oauth/authorize"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.launchmystore.io/apps/oauth/authorize', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.launchmystore.io/apps/oauth/authorize",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.launchmystore.io/apps/oauth/authorize"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.launchmystore.io/apps/oauth/authorize")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.launchmystore.io/apps/oauth/authorize")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"status": 123,
"state": "<string>",
"data": {
"code": "<string>",
"state": "<string>",
"redirectUri": "<string>",
"app": {}
}
}Authorize
Starts the OAuth 2.0 authorization code grant flow. The merchant must be signed in to their LaunchMyStore admin when this endpoint is hit — the endpoint is protected by merchant auth and uses the merchant’s session to resolve whichstoreId is granting consent. On success, the endpoint
returns a one-time code plus an opaque state your app must round-trip
to POST /apps/oauth/token to exchange for an access token.
Apps don’t call this API directly. To request authorization from a
merchant, redirect their browser to the admin consent page —
https://app.launchmystore.io/oauth/authorize — with the same query
parameters. The consent page authenticates the merchant, shows your app’s
name and requested scopes, and calls this API on Approve; the browser then
lands on your redirect_uri with ?code=&state= (plus client_state=
echoing your own nonce, if you sent one), or ?error=access_denied on
Cancel. See Authentication for the full
browser flow. Before rendering, the consent page validates your
client_id + redirect_uri via the public
GET /apps/oauth/client-info?client_id=&redirect_uri= endpoint, which
returns only your app’s public listing fields (name, developer, icon,
registered scopes).The
state parameter returned by this endpoint is server-generated
and bound to the authorization code server-side with a 10-minute TTL. You
must echo it back on the token exchange call. Your own anti-CSRF token,
if any, should be passed in the request as a separate value and is not
read by the server.Request
curl -X GET "https://api.launchmystore.io/apps/oauth/authorize?\
client_id=lms_app_xxx&\
redirect_uri=https://your-app.com/oauth/callback&\
scope=read_products,write_products,read_orders&\
response_type=code&\
code_challenge=BASE64URL(SHA256(verifier))&\
code_challenge_method=S256" \
-H "Authorization: Bearer MERCHANT_JWT"
Query Parameters
string
required
The app’s public client identifier. Issued when the developer registers
the app via
POST /apps/developer/create.string
required
The callback URL where the merchant will be redirected after consent.
Must exactly match one of the URLs in
app.redirectUrls. Mismatched URIs
return 400 Invalid redirect URI.string
required
Comma-separated list of scopes being requested (e.g.
read_products,write_orders). Each scope must be a member of the app’s
registered scope set; otherwise the endpoint returns
400 Invalid scopes: <list>. See Scopes
for the 37 available scopes.string
default:"code"
Must be
code. Any other value returns
400 Unsupported response_type. Defaults to code when omitted for
back-compat.string
Optional client-side anti-CSRF nonce. The server stores this in
clientState against the issued code but does not echo it back in
the response — the response’s data.state is always the
server-generated token. Not used for server-side validation.string
PKCE (RFC 7636) code challenge. When supplied, the matching
code_verifier must be sent on the token exchange. Length must be
43-128 characters.string
default:"plain"
Either
S256 (strongly recommended) or plain. Any other value
returns 400. Only meaningful when code_challenge is also sent.Response
integer
HTTP status code (200 on success).
string
Final response state:
"success" or "error".object
Show Data
Show Data
string
Single-use authorization code. 64-character hex string. Expires in
10 minutes.
string
Server-generated state token. Must be echoed back to the token
endpoint.
string
The validated redirect URI for this app.
object
Public-safe metadata about the app being authorized (name,
description, developer, iconUrl, requested scopes).
Example Response
{
"status": 200,
"state": "success",
"data": {
"code": "9fbb1c3e8d4a7b2e0a5d6f7c8b9e0d1c2a3b4c5d6e7f8091a2b3c4d5e6f70819",
"state": "5d6f7c8b9e0d1c2a3b4c5d6e7f80910a2b3c4d5e6f70819b8c4d5e6f7081923a",
"redirectUri": "https://your-app.com/oauth/callback",
"app": {
"name": "Foundry Reviews",
"description": "Customer reviews with photos and Q&A",
"developer": "Foundry Apps",
"iconUrl": "https://cdn.launchmystore.io/apps/foundry-reviews/icon.png",
"scopes": ["read_products", "write_metafields", "read_orders"]
}
}
}
redirect_uri with
code and state appended as query parameters, then immediately call
POST /apps/oauth/token server-side.
PKCE Flow (recommended for public clients)
import crypto from 'crypto';
// 1. Generate verifier + challenge
const codeVerifier = crypto.randomBytes(32).toString('base64url');
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');
// 2. Authorize with the challenge
const authUrl = new URL('https://api.launchmystore.io/apps/oauth/authorize');
authUrl.searchParams.set('client_id', CLIENT_ID);
authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
authUrl.searchParams.set('scope', 'read_products,write_metafields');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
// 3. On the callback, exchange code + verifier. NOTE: `client_secret`
// is ALWAYS required at the token endpoint — PKCE is enforced in
// addition to the secret, not instead of it, so the exchange must
// run server-side. See: /api-reference/oauth/token
Error Codes
| HTTP | Error message | When |
|---|---|---|
400 | Invalid redirect URI | redirect_uri not in app.redirectUrls |
400 | Invalid scopes: <names> | One or more requested scopes not registered on the app |
400 | Unsupported response_type | response_type is anything other than code |
400 | Invalid code_challenge_method | Not S256 or plain |
400 | code_challenge must be 43-128 characters | PKCE challenge length out of range |
401 | (auth guard) | No valid merchant session on the request |
404 | App not found or suspended | client_id unknown, or the app has been suspended by admin. Publish status does not gate authorization — draft/in-review apps can complete OAuth on the developer’s own store |
Security Notes
- Authorization codes expire after 10 minutes and are
single-use. Replays of an already-consumed code fail with
Invalid or expired authorization codeon the token endpoint. - The server-generated
stateis bound to thecodeserver-side. Both are deleted atomically when the token is issued. - This endpoint is not rate-limited at the gateway — it is already gated by merchant authentication.
- For embedded apps, prefer PKCE + session tokens. See Session Tokens.