> ## 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.

# Authentication

> Secure your app with OAuth 2.0

# OAuth 2.0 Authentication

LaunchMyStore uses OAuth 2.0 for secure app authentication. This allows merchants to grant your app specific permissions without sharing their credentials.

## Two install paths

Depending on how the merchant reaches your app, one of two OAuth flows runs.

### A. Managed install — the merchant clicks Install in our marketplace

The merchant sees the consent screen inside LaunchMyStore's admin, approves the scopes, and we redirect them straight to your `/auth` endpoint with a pre-authorized `code`. **You do not call `/oauth/authorize` in this flow** — the consent already happened.

```mermaid theme={null}
sequenceDiagram
    participant Merchant
    participant LaunchMyStore
    participant YourApp

    Merchant->>LaunchMyStore: Clicks Install in marketplace
    LaunchMyStore->>Merchant: Shows permission screen (inside our admin)
    Merchant->>LaunchMyStore: Approves "Allow & Install"
    LaunchMyStore->>YourApp: Redirect to /auth?shop=&storeId=&code=&state=&hmac=...
    YourApp->>YourApp: Verify HMAC
    YourApp->>LaunchMyStore: POST /apps/oauth/token { code, state, ... }
    LaunchMyStore->>YourApp: { access_token, refresh_token }
    YourApp->>Merchant: Redirect back into the admin (decoded `host`)
```

See [Install handoff (`/auth`)](/getting-started/install-handoff) for the full contract: HMAC verification, exchange semantics, code samples in Node/Python/Ruby.

### B. Re-authorization — your app needs a fresh grant later

When your app needs a fresh grant after install (lost tokens, expired
refresh token, or a scope upgrade), redirect the merchant's **browser** to
the admin consent page:

```
https://app.launchmystore.io/oauth/authorize
  ?client_id=lms_app_xxxxxxxxxxxx
  &redirect_uri=https://your-app.com/auth/callback   # must be registered on your app
  &scope=read_products,write_products                # optional — defaults to registered scopes
  &state=<your own anti-CSRF nonce>                  # optional, echoed back as client_state
  &code_challenge=...&code_challenge_method=S256     # optional PKCE
```

The merchant logs in (if needed), reviews your app's name, developer and
requested scopes on the consent screen, and clicks **Authorize**. The
browser is then redirected to your `redirect_uri` with:

| Param          | Meaning                                                                                                                                     |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `code`         | Single-use authorization code (10-minute TTL).                                                                                              |
| `state`        | **Server-generated** token — echo this exact value at `POST /apps/oauth/token`.                                                             |
| `client_state` | Your own `state` nonce, passed through verbatim (only if you sent one). Use it to bind the callback to the merchant's session on your side. |

If the merchant clicks Cancel you receive
`redirect_uri?error=access_denied&state=<your nonce>`.

```mermaid theme={null}
sequenceDiagram
    participant YourApp
    participant Merchant
    participant LaunchMyStore

    YourApp->>Merchant: Redirect to app.launchmystore.io/oauth/authorize?client_id=...
    Merchant->>LaunchMyStore: Reviews consent screen, clicks Authorize
    LaunchMyStore->>YourApp: Redirect to redirect_uri?code=&state=&client_state=
    YourApp->>LaunchMyStore: POST /apps/oauth/token { code, state, ... }
    LaunchMyStore->>YourApp: { data: { access_token, refresh_token } }
```

<Note>
  The consent page is `app.launchmystore.io/oauth/authorize` (the admin UI).
  The **API** endpoint `api.launchmystore.io/apps/oauth/authorize` is
  merchant-JWT-authenticated JSON that the consent page calls internally —
  redirecting a browser directly to the API returns `401`. A merchant
  **reinstall** also re-runs the managed `/auth` handoff and delivers a fresh
  code, so either path recovers a lost token; prefer the consent redirect for
  scope upgrades (no uninstall involved).
</Note>

## Available Scopes

Request only the scopes your app needs. Merchants see all requested scopes during installation.

### Store Data

| Scope               | Description                     |
| ------------------- | ------------------------------- |
| `read_shop`         | View store information          |
| `write_shop`        | Modify store settings           |
| `read_products`     | View products and variants      |
| `write_products`    | Create, update, delete products |
| `read_collections`  | View collections                |
| `write_collections` | Manage collections              |

### Orders & Customers

| Scope                | Description                    |
| -------------------- | ------------------------------ |
| `read_orders`        | View orders and transactions   |
| `write_orders`       | Create, update, fulfill orders |
| `read_customers`     | View customer data             |
| `write_customers`    | Create, update customers       |
| `read_fulfillments`  | View fulfillment data          |
| `write_fulfillments` | Create, update fulfillments    |

### Inventory & Discounts

| Scope             | Description              |
| ----------------- | ------------------------ |
| `read_inventory`  | View inventory levels    |
| `write_inventory` | Adjust inventory         |
| `read_discounts`  | View discount codes      |
| `write_discounts` | Create, manage discounts |

### Metafields

| Scope              | Description               |
| ------------------ | ------------------------- |
| `read_metafields`  | View metafield data       |
| `write_metafields` | Create, update metafields |

### Content & Files

| Scope           | Description                                                                                      |
| --------------- | ------------------------------------------------------------------------------------------------ |
| `read_files`    | List and download merchant-uploaded files *(reserved — `/api/v1/files` endpoints not yet bound)* |
| `write_files`   | Upload, replace, delete files *(reserved — `/api/v1/files` endpoints not yet bound)*             |
| `read_themes`   | Read theme files and settings                                                                    |
| `write_themes`  | Install themes, edit theme files, toggle app embeds                                              |
| `read_content`  | View blogs, articles, pages                                                                      |
| `write_content` | Create / update / delete blogs, articles, pages                                                  |

<Note>
  Scopes marked *reserved* are declared in the OAuth catalog and can be
  requested by an app today, but the corresponding REST endpoints are not yet
  exposed under `/api/v1/`. Request them now if you want forward-compat;
  the API surface will land before the v1 freeze.
</Note>

### Shipping & Gift Cards

| Scope              | Description                                       |
| ------------------ | ------------------------------------------------- |
| `read_shipping`    | View shipping zones, methods, and rates           |
| `write_shipping`   | Create / update / delete shipping zones and rates |
| `read_gift_cards`  | View issued gift cards and balances               |
| `write_gift_cards` | Issue, refund, or void gift cards                 |

### Billing, Analytics, Settings & Marketing

| Scope             | Description                                                 |
| ----------------- | ----------------------------------------------------------- |
| `read_billing`    | View merchant subscriptions and invoices                    |
| `write_billing`   | Create one-time charges and recurring subscription products |
| `read_analytics`  | Query the analytics / reports API                           |
| `read_settings`   | View store-wide settings (taxes, payments, locale)          |
| `write_settings`  | Modify store-wide settings                                  |
| `read_marketing`  | View campaigns, segments, abandoned-cart data               |
| `write_marketing` | Create / update marketing campaigns                         |

### Email Templates

| Scope                   | Description                                                                                                                                      |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `read_email_templates`  | Read transactional email templates registered by your app                                                                                        |
| `write_email_templates` | Override transactional email templates (order confirmation, password reset, etc.) — see [Email Templates extension](/extensions/email-templates) |

## Authorization Request

The authorize endpoint is a **merchant-authenticated JSON API** — it is
called by the LaunchMyStore admin (with the merchant's JWT), not by your
app, and it does not redirect the browser:

```
GET https://api.launchmystore.io/apps/oauth/authorize
Authorization: Bearer <merchant admin JWT>
```

Your app receives the resulting `code` + `state` through the managed
[install handoff](/getting-started/install-handoff) redirect to your
`/auth` endpoint.

### Parameters

| Parameter               | Required | Description                                                             |
| ----------------------- | -------- | ----------------------------------------------------------------------- |
| `client_id`             | Yes      | Your app's Client ID                                                    |
| `scope`                 | Yes      | Comma-separated list of scopes                                          |
| `redirect_uri`          | Yes      | Your callback URL (must match registered URL)                           |
| `state`                 | Optional | Client-side nonce, stored server-side as `clientState`; not echoed back |
| `response_type`         | Optional | Must be `code` (default)                                                |
| `code_challenge`        | Optional | PKCE (RFC 7636) challenge, 43-128 chars                                 |
| `code_challenge_method` | Optional | `S256` (recommended) or `plain`                                         |

On success it returns JSON: `{ status, state: "success", data: { code,
state, redirectUri, app } }`. The server-generated `data.state` must be
echoed back on the token exchange. See
[Authorize](/api-reference/oauth/authorize) for the full contract.

## Token Exchange

After the merchant approves, they're redirected to your `redirect_uri` with a `code` parameter. Exchange this for tokens:

```
POST https://api.launchmystore.io/apps/oauth/token
Content-Type: application/json

{
  "client_id": "your_client_id",
  "client_secret": "your_client_secret",
  "code": "authorization_code_from_callback",
  "grant_type": "authorization_code",
  "state": "state_from_callback"
}
```

### Response

The token payload is wrapped in the standard platform envelope
(`{ status, state, message, data }`) — read the fields from `data`:

```json theme={null}
{
  "status": 200,
  "state": "success",
  "message": null,
  "data": {
    "access_token": "lms_token_xxxxxxxxxxxxxxxxxxxxxxxx",
    "refresh_token": "lms_refresh_xxxxxxxxxxxxxxxxxxxxxxxx",
    "token_type": "bearer",
    "expires_in": 86400,
    "scope": "read_products write_products read_orders"
  }
}
```

```javascript theme={null}
const { data } = await tokenResponse.json();
// data.access_token, data.refresh_token, data.scope ...
```

<Note>
  `data.scope` is a **space-delimited** string (e.g. `"read_products write_orders"`) —
  split on a space, not a comma. This is the merchant's **granted** scopes;
  persist it as your authorization source (see below). `token_type` is returned
  lowercase (`bearer`).
</Note>

## Token Refresh

Access tokens expire after 24 hours. Use the refresh token to get a new access token:

```
POST https://api.launchmystore.io/apps/oauth/token
Content-Type: application/json

{
  "client_id": "your_client_id",
  "client_secret": "your_client_secret",
  "refresh_token": "your_refresh_token",
  "grant_type": "refresh_token"
}
```

<Warning>
  Refresh tokens expire after 30 days. If a refresh token expires, the merchant must re-authorize your app.
</Warning>

<Warning>
  **Refresh tokens rotate.** Each refresh returns a **new** `refresh_token` and
  invalidates the one you just used. Always persist the newest `refresh_token`
  from the response and discard the old one — reusing a spent refresh token
  returns `401`.
</Warning>

<Note>
  An expired **access** token does not require re-authorization or reinstalling
  the app — just call the refresh endpoint with a still-valid refresh token to
  get a new one.
</Note>

## Making Authenticated Requests

Include the access token in the `Authorization` header:

```javascript theme={null}
const response = await fetch('https://api.launchmystore.io/api/v1/products.json', {
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'Content-Type': 'application/json'
  }
});
```

## Security Best Practices

<AccordionGroup>
  <Accordion title="Store tokens securely">
    Never store tokens in client-side code or version control. Use encrypted database columns or a secrets manager.
  </Accordion>

  <Accordion title="Validate the state parameter">
    Always validate that the `state` parameter in the callback matches what you sent. This prevents CSRF attacks.
  </Accordion>

  <Accordion title="Handle token expiration">
    Implement automatic token refresh before making API calls. Check `expires_in` and refresh proactively.
  </Accordion>

  <Accordion title="Request minimal scopes">
    Only request the permissions your app actually needs. Merchants are more likely to install apps with fewer permissions.
  </Accordion>
</AccordionGroup>
