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

# Install flow (OAuth 2.1 + PKCE)

> Implement the authorization-code-with-PKCE flow so merchants can install your app and your server can call the Salesive API.

Salesive apps authorize with the **OAuth 2.1 authorization code flow** and **PKCE is
mandatory** (`S256`). This page walks through the full handshake: redirecting the merchant to
consent, receiving an authorization code, and exchanging it for tokens.

## Endpoints

Salesive publishes an OAuth 2.1 discovery document. Fetch it once and read the endpoints from
it rather than hardcoding paths:

```bash theme={null}
curl https://api.salesive.com/.well-known/oauth-authorization-server
```

```json theme={null}
{
  "issuer": "https://api.salesive.com",
  "authorization_endpoint": "https://app.salesive.com/apps/authorize",
  "token_endpoint": "https://api.salesive.com/api/v1/oauth/token",
  "revocation_endpoint": "https://api.salesive.com/api/v1/oauth/revoke",
  "registration_endpoint": "https://api.salesive.com/api/v1/oauth/clients",
  "scopes_supported": ["READ_ORDERS", "WRITE_ORDERS", "..."],
  "code_challenge_methods_supported": ["S256"],
  "grant_types_supported": ["authorization_code", "refresh_token"]
}
```

<Note>
  The `authorization_endpoint` is on the Salesive **dashboard** host
  (`app.salesive.com`) — that's where the merchant approves. The `token_endpoint` and all API
  calls are on the **API** host (`api.salesive.com`). Always read these from the discovery
  document rather than hardcoding.
</Note>

| Endpoint                 | Purpose                                                              |
| ------------------------ | -------------------------------------------------------------------- |
| `authorization_endpoint` | Where you send the merchant to approve the install (consent screen). |
| `token_endpoint`         | `POST` to exchange a code for tokens, or to refresh.                 |
| `revocation_endpoint`    | `POST` to revoke a token (RFC 7009).                                 |

<Note>
  You'll need your `client_id` and `client_secret` from the **Developer console**
  (Apps → Developer). See [Build & publish](/apps/building-publishing).
</Note>

## Prerequisites

* A registered app with a `client_id`, `client_secret`, and at least one **redirect URI**.
* A server endpoint that can receive the OAuth callback and hold the `client_secret`.
* The scopes your app needs — see [Scopes & permissions](/apps/scopes-permissions).

## Step 1 · Generate a PKCE pair

For each install attempt, generate a random `code_verifier` and derive its `code_challenge`.
Keep the verifier server-side (or in the session) — you'll need it at token exchange.

```javascript theme={null}
import crypto from "crypto";

const base64url = (buf) =>
  buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");

// 43–128 chars
const codeVerifier = base64url(crypto.randomBytes(32));
const codeChallenge = base64url(
  crypto.createHash("sha256").update(codeVerifier).digest()
);
```

<Warning>
  PKCE is required. Requests without a valid `code_challenge` (method `S256`) are rejected.
</Warning>

## Step 2 · Redirect the merchant to consent

Send the merchant's browser to the `authorization_endpoint` with your request parameters.
The merchant — who is signed in to Salesive — sees a consent screen listing the scopes you
requested and the store they're installing on, then approves.

```text theme={null}
GET {authorization_endpoint}
  ?response_type=code
  &client_id=app_xxxxxxxxxxxxxxxx
  &redirect_uri=https://yourapp.com/oauth/callback
  &scope=READ_ORDERS%20WRITE_ORDERS%20READ_INVENTORY
  &code_challenge=<code_challenge>
  &code_challenge_method=S256
  &state=<opaque-random-state>
```

| Parameter               | Required    | Notes                                                             |
| ----------------------- | ----------- | ----------------------------------------------------------------- |
| `response_type`         | Yes         | Always `code`.                                                    |
| `client_id`             | Yes         | Your app's client id (`app_…`).                                   |
| `redirect_uri`          | Yes         | Must **exactly** match one of your registered redirect URIs.      |
| `scope`                 | Yes         | Space-separated list of scopes. Request only what you need.       |
| `code_challenge`        | Yes         | The PKCE challenge from Step 1.                                   |
| `code_challenge_method` | Yes         | Must be `S256`.                                                   |
| `state`                 | Recommended | Opaque value you generate; verify it on callback to prevent CSRF. |

On approval, Salesive creates the **installation** on the merchant's active store and redirects
back to your `redirect_uri` with a `code` (and your `state`). On denial, it redirects with
`?error=access_denied`.

```text theme={null}
https://yourapp.com/oauth/callback?code=<authorization_code>&state=<your-state>
```

## Step 3 · Exchange the code for tokens

From your **server**, `POST` to the `token_endpoint`. Send your `client_secret` and the PKCE
`code_verifier` you stored in Step 1.

```bash theme={null}
curl -X POST https://api.salesive.com/api/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "code": "<authorization_code>",
    "redirect_uri": "https://yourapp.com/oauth/callback",
    "code_verifier": "<code_verifier>",
    "client_id": "app_xxxxxxxxxxxxxxxx",
    "client_secret": "sk_xxxxxxxxxxxxxxxx"
  }'
```

```json theme={null}
{
  "access_token": "app_4f3c...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "app_rt_9a21...",
  "scope": "READ_ORDERS WRITE_ORDERS READ_INVENTORY"
}
```

Store the `refresh_token` securely (encrypted at rest), keyed to the merchant/store. The
`access_token` is short-lived (one hour).

<Note>
  Client credentials may also be sent as HTTP Basic auth
  (`Authorization: Basic base64(client_id:client_secret)`) instead of in the body.
</Note>

## Step 4 · Call the API

Send the access token as a bearer token. You do **not** need to specify the store — the token
is already bound to the store it was installed on.

```bash theme={null}
curl https://api.salesive.com/api/v1/orders \
  -H "Authorization: Bearer app_4f3c..."
```

See [Scopes & permissions](/apps/scopes-permissions) for the full list of endpoints your app
can reach and the scope each one requires.

## Step 5 · Refresh the access token

When the access token expires, exchange your refresh token for a new pair. Refresh tokens
**rotate** — the old refresh token is invalidated and you receive a new one, so always persist
the latest.

```bash theme={null}
curl -X POST https://api.salesive.com/api/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "refresh_token",
    "refresh_token": "app_rt_9a21...",
    "client_id": "app_xxxxxxxxxxxxxxxx",
    "client_secret": "sk_xxxxxxxxxxxxxxxx"
  }'
```

## Handling uninstalls & revocation

A merchant can uninstall your app at any time from **Apps → Installed apps** in their
dashboard. When they do, the installation is deactivated and **all of your app's tokens for
that store stop working immediately** — even an access token that hasn't expired yet.

Treat a `401 Unauthorized` with a message like *"This app is no longer installed on the
store"* as an uninstall signal: stop calling the API for that store and clean up your stored
tokens.

You can also proactively revoke a token:

```bash theme={null}
curl -X POST https://api.salesive.com/api/v1/oauth/revoke \
  -H "Content-Type: application/json" \
  -d '{ "token": "app_rt_9a21..." }'
```

## Common errors

| Status                      | Meaning                                                 | Fix                                                                   |
| --------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------- |
| `400 invalid_grant`         | Code expired/used, or `code_verifier` doesn't match.    | Restart the flow; codes are single-use and expire in 10 minutes.      |
| `400 redirect_uri_mismatch` | The `redirect_uri` isn't registered.                    | Add the exact URI in the Developer console.                           |
| `401 invalid_client`        | Wrong `client_id`/`client_secret`.                      | Check credentials; regenerate the secret if needed.                   |
| `401` on an API call        | Token expired or app uninstalled.                       | Refresh the token, or treat as uninstalled.                           |
| `403` on an API call        | Missing scope, or the endpoint isn't available to apps. | Request the scope at install; see [Scopes](/apps/scopes-permissions). |

## Next steps

<CardGroup cols={2}>
  <Card title="Scopes & permissions" icon="key" href="/apps/scopes-permissions">
    Map each scope to the endpoints it unlocks.
  </Card>

  <Card title="Build & publish" icon="rocket" href="/apps/building-publishing">
    Register, list, and submit your app for review.
  </Card>
</CardGroup>
