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

# Installing Your App

> The exact install flow for public and private apps — every credential, header, and call, and how to automate it.

Installation is what links your app to a specific store. Until an installation exists, the [client credentials flow](/developer/authentication#client-credentials-flow) rejects the store with `403 App is not installed on this store` — so this page is step zero for every integration.

There are two ways an installation gets created, and they end in the same place:

| Path                                              | Who drives it                                         | Best for                            |
| ------------------------------------------------- | ----------------------------------------------------- | ----------------------------------- |
| [App Marketplace UI](#path-a-marketplace-install) | A dashboard user clicks **Install**                   | Merchant self-serve, manual testing |
| [Install API](#path-b-api-install)                | Your backend calls `POST /v1/oauth/authorize/approve` | Automated onboarding                |

Both paths work for public and private apps. After either one, you mint per-store tokens with `client_credentials` exactly the same way.

## Prerequisites

Before installing on a store, you need all of the following:

1. **An app with credentials.** Created in the developer dashboard — see [Creating an App](/developer/creating-an-app). You need the `client_id`, and the plaintext `client_secret` shown once at creation or rotation (`op_secret_...`).
2. **The app approved.** Public apps go through marketplace review; private apps only need review when they request admin-gated scopes. An app still in review cannot be installed.
3. **Access to the target store.** For your own account's stores you have this already. For a merchant's store, your organization needs an approved [collaboration](/developer/organizations#get-access-to-a-merchants-store) — once the merchant approves, everyone in your org holds collaborator access to that store.
4. **A live redirect URI.** The **first** redirect URI registered on your app receives the authorization code on marketplace installs (see [code delivery](#how-the-authorization-code-reaches-you)). It must be a public HTTPS endpoint.
5. **The store ID** (a cuid like `cms6hqe230006gifyusgheils`). We share it when provisioning your stores; it also appears in dashboard API responses for any store you can access.

## Path A — Marketplace install

The **App Marketplace** appears in the dashboard's left sidebar for users whose role carries marketplace access: account owners, developer-org members, and store collaborators. Approved public apps are listed for every merchant; your private apps appear under the **Private** tab for your own stores.

1. Select the store, open **App Marketplace**, and find your app.
2. Click **Install**. A consent dialog lists every requested scope — required scopes are locked on, optional scopes can be unchecked.
3. On confirm, OrderProtection creates the installation and delivers an authorization code to your server (details [below](#how-the-authorization-code-reaches-you)).

Because collaborators get marketplace access, your org can install your own app on a collaborated merchant store through this same UI — no merchant action needed beyond the collaboration approval.

### How the authorization code reaches you

On a marketplace install, code delivery is **server-to-server** — the merchant's browser never touches your domain. OrderProtection's backend sends:

```
GET {your first redirect URI}?code=<authorization code>&store_id=<store id>
```

Three things to build your callback handler around:

* **You must respond `2xx` within 10 seconds.** Any other response (or a timeout) makes the whole install fail and roll back — the merchant sees an error and no installation exists. Keep the handler fast: acknowledge first, process async.
* **There is no `state` parameter** on this delivery, and the request is not signed. Treat the callback as a hint, not proof: the code is only trustworthy once you exchange it (the exchange requires your `client_secret`).
* The code is **single-use and expires in 10 minutes**.

You can exchange the code via the [authorization code grant](/developer/authentication#2-exchange-code-for-tokens), or ignore it and mint [client credentials](#mint-tokens) tokens — the installation exists either way.

## Path B — API install

For automated onboarding, your backend performs the install directly. The endpoint that creates an installation is:

```
POST /v1/oauth/authorize/approve
```

It is a **dashboard-user endpoint**: it requires the bearer token of a logged-in OrderProtection user who has access to the target store. It rejects every other credential — `op_at_` app access tokens, `op_pat_` personal access tokens, embedded session tokens, and store API keys all return `401`.

### Step 1 — Obtain a user token

Log in programmatically with a dashboard user's credentials (we recommend a dedicated service user in your org):

```bash theme={null}
curl -X POST https://api.production.orderprotection.com/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "svc@yourcompany.com",
    "password": "..."
  }'
```

```json theme={null}
{
  "data": {
    "access_token": "eyJhbGciOiJSUzI1NiIs...",
    "refresh_token": "...",
    "expires_in": 86400,
    "token_type": "Bearer"
  },
  "status": 200
}
```

`data.access_token` is the bearer token for the install call. Notes for automation:

* The token expires after `expires_in` seconds. Refresh with `POST /v1/auth/refresh` `{"refreshToken": "..."}` — it returns a fresh `access_token`; keep reusing the original refresh token.
* If the account has MFA enabled, the login response returns `{"mfaRequired": true, ...}` instead of tokens. Use a service user without MFA, or contact us about the TOTP flow.
* Store access is evaluated fresh on every request, so a collaboration approved *after* login is visible to an existing token immediately — no re-login needed.

### Step 2 — Install

```bash theme={null}
curl -X POST https://api.production.orderprotection.com/v1/oauth/authorize/approve \
  -H "Authorization: Bearer <access_token from step 1>" \
  -H "Content-Type: application/json" \
  -d '{
    "clientId": "op_app_...",
    "redirectUri": "https://yourapp.example.com/callback",
    "scopes": ["store:read", "partner-context:update", "..."],
    "storeId": "<store id>"
  }'
```

Rules the request must satisfy:

* `redirectUri` must **exactly** string-match a redirect URI registered on the app — no wildcards, no trailing-slash forgiveness.
* `scopes` must include **every scope the app marks as required** (for most apps, all of them) and nothing the app doesn't request. Short lists fail with `Required scopes cannot be denied`; extra scopes fail with `Requested scopes exceed application permissions`.
* `storeId` must be a store the authenticated user can access (via role or approved collaboration).

```json theme={null}
{
  "data": {
    "code": "a1b2c3d4e5f6...",
    "redirectUri": "https://yourapp.example.com/callback?code=a1b2c3d4e5f6..."
  },
  "status": 201
}
```

Unlike the marketplace path, the code comes back **in the response body** — nothing calls your redirect URI. Exchange it, or skip straight to client credentials.

The call is idempotent-friendly: re-running it against a store that already has the app installed does not error — it refreshes the installation and re-syncs the scope grants.

## Mint tokens

Once the installation exists (either path), mint one token per store:

```bash theme={null}
curl -X POST https://api.production.orderprotection.com/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "op_app_...",
    "client_secret": "op_secret_...",
    "store_id": "<store id>"
  }'
```

`client_secret` is the plaintext value shown once at creation or rotation — see [Authentication](/developer/authentication#client-credentials-flow) for token lifetimes and refresh.

## Uninstalling

Merchants (and your org, on collaborated or own stores) uninstall from **App Marketplace → Installed**. The API equivalent, with the same dashboard-user auth as the install call:

```
DELETE /v1/stores/{storeId}/apps/{installationId}
```

On uninstall, all of the installation's access and refresh tokens are revoked **immediately**, and registered app webhooks for the store are removed.

To be notified of uninstalls, add the `app/uninstalled` topic to your app's webhook settings — it is delivered directly to your webhook URL for every installation, with the store and installation ids in the body. See [Webhooks](/developer/webhooks#app-lifecycle). As a fallback, handle `401` responses gracefully or poll `GET /v1/developer/apps/{appId}/installations` (dashboard-user auth) and diff the list.

Reinstalling after an uninstall works through either path with no special handling — the installation is recreated cleanly and new tokens mint as normal. This makes install → uninstall → install cycles safe for testing.

## Common errors

| Response                                              | Where                                      | Cause and fix                                                                                                                                                                    |
| ----------------------------------------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401 Unauthorized`                                    | `authorize/approve`, `DELETE .../apps/...` | Missing, expired, or wrong-type bearer token. Only a dashboard user JWT works — app tokens (`op_at_`), PATs (`op_pat_`), and session tokens are rejected. Log in again (Step 1). |
| `403 You do not have access to this store`            | `authorize/approve`                        | The authenticated user has no role on that `storeId`. For merchant stores: the collaboration isn't approved yet.                                                                 |
| `400 Required scopes cannot be denied: ...`           | `authorize/approve`                        | Your `scopes` array is missing required scopes. Send the app's full scope list.                                                                                                  |
| `400 Requested scopes exceed application permissions` | `authorize/approve`                        | Your `scopes` array contains a scope the app doesn't request. Match it to the app's declared scopes exactly.                                                                     |
| `400 Invalid redirect URI`                            | `authorize/approve`                        | `redirectUri` doesn't exactly match a registered URI.                                                                                                                            |
| `400 Application is not approved`                     | `authorize/approve`                        | The app is still in review (or draft).                                                                                                                                           |
| `403 App is not installed on this store`              | `POST /v1/oauth/token`                     | The install step was skipped or targeted a different store.                                                                                                                      |
| `502` on marketplace install                          | Marketplace UI                             | Your redirect URI didn't return `2xx` within 10 seconds; the install rolled back. Fix the callback and retry.                                                                    |
