> ## Documentation Index
> Fetch the complete documentation index at: https://docs.launchmystore.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Authorize

> Start the OAuth 2.0 authorization code flow for a merchant

# 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 which `storeId` 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.

<Note>
  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.
</Note>

## Request

```bash theme={null}
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

<ParamField query="client_id" type="string" required>
  The app's public client identifier. Issued when the developer registers
  the app via `POST /apps/developer/create`.
</ParamField>

<ParamField query="redirect_uri" type="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`.
</ParamField>

<ParamField query="scope" type="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](/api-reference/oauth/scopes)
  for the 37 available scopes.
</ParamField>

<ParamField query="response_type" type="string" default="code">
  Must be `code`. Any other value returns
  `400 Unsupported response_type`. Defaults to `code` when omitted for
  back-compat.
</ParamField>

<ParamField query="state" type="string">
  Optional client-side anti-CSRF nonce. The server stores this in
  `clientState` and returns it untouched alongside the server-generated
  `state` for your reference. Not used for server-side validation.
</ParamField>

<ParamField query="code_challenge" type="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.
</ParamField>

<ParamField query="code_challenge_method" type="string" default="plain">
  Either `S256` (strongly recommended) or `plain`. Any other value
  returns `400`. Only meaningful when `code_challenge` is also sent.
</ParamField>

## Response

<ResponseField name="status" type="integer">
  HTTP status code (200 on success).
</ResponseField>

<ResponseField name="state" type="string">
  Final response state: `"success"` or `"error"`.
</ResponseField>

<ResponseField name="data" type="object">
  <Expandable title="Data">
    <ResponseField name="code" type="string">
      Single-use authorization code. 64-character hex string. Expires in
      10 minutes.
    </ResponseField>

    <ResponseField name="state" type="string">
      Server-generated state token. Must be echoed back to the token
      endpoint.
    </ResponseField>

    <ResponseField name="redirectUri" type="string">
      The validated redirect URI for this app.
    </ResponseField>

    <ResponseField name="app" type="object">
      Public-safe metadata about the app being authorized (name,
      description, developer, iconUrl, requested scopes).
    </ResponseField>
  </Expandable>
</ResponseField>

## Example Response

```json theme={null}
{
  "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"]
    }
  }
}
```

Your app should now redirect the merchant back to your `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)

```javascript theme={null}
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 (no client_secret needed for PKCE)
//    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 not published`           | `client_id` does not match a published app             |

## Security Notes

* Authorization codes expire after **10 minutes** and are
  single-use. Replays of an already-consumed code fail with
  `Invalid or expired authorization code` on the token endpoint.
* The server-generated `state` is bound to the `code` server-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](/api-reference/authentication#session-tokens).
