# OAuth apps

> Publish an integration that any ShopFast store can connect, with OAuth 2.1, PKCE, scoped tokens and rotating refresh tokens.

Source: https://shopfa.st/docs/guides/oauth

Use OAuth when **other sellers** install your integration. For your own store, an [API key](https://shopfa.st/docs/guides/authentication)
is simpler.

## Register an app

In **Settings → Developers → OAuth apps**, choose **New app** and fill in:

- **Name, description, homepage**: shown on the consent screen with your store's name as the publisher.
- **Redirect URIs**: matched exactly, character for character. `https` only, except `http://127.0.0.1`,
  `http://localhost` and `http://[::1]` for native and CLI apps. No fragments.
- **The most it may ask for**: scopes per area, like API keys. A request for more is quietly capped.

You get a **client id** (`sfc_…`) and a **client secret** (`sfcs_…`, shown once). Rotating the secret stops the old one
at once. Turning the app off stops every token it issued, everywhere, until you turn it back on.

## 1. Send the seller to the consent screen

Create a PKCE verifier (43 to 128 random URL-safe characters) and its S256 challenge, then redirect:

```http
GET https://app.shopfa.st/oauth/authorize
  ?client_id=sfc_…
  &redirect_uri=https://your-app.example/callback
  &response_type=code
  &scope=catalog:read orders:read
  &state=<random, tied to the browser session>
  &code_challenge=<base64url(sha256(verifier))>
  &code_challenge_method=S256
```

The seller signs in if needed, picks one of their stores (owners and admins can approve) and sees exactly what your
app may do. **Cancel** redirects with `error=access_denied`.

## 2. Exchange the code

On approval the browser lands on your redirect URI with `code`, your `state` and `store_id`. Check `state`, then
exchange the code within 10 minutes. It works once.

<div class="tabs">

```sh
curl -X POST https://api.shopfa.st/v1/oauth/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=authorization_code \
  -d code="$CODE" \
  -d redirect_uri=https://your-app.example/callback \
  -d code_verifier="$VERIFIER"
```

```ts
const res = await fetch('https://api.shopfa.st/v1/oauth/token', {
  method: 'POST',
  headers: { 'content-type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'authorization_code', code, redirect_uri: REDIRECT_URI, code_verifier: verifier,
    client_id: CLIENT_ID, client_secret: CLIENT_SECRET,
  }),
});
const tokens = await res.json(); // { access_token, refresh_token, expires_in: 3600, scope, store_id }
```

</div>

```json
{
  "access_token": "sfo_…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "sfr_…",
  "scope": "catalog:read orders:read",
  "store_id": "st_…"
}
```

Client authentication works with HTTP Basic or `client_id` / `client_secret` in the body. The body can be form-encoded
or JSON.

## 3. Call the API

```sh
curl https://api.shopfa.st/v1/store/products -H "Authorization: Bearer sfo_…"
```

The token reaches only the store that approved it and only the granted scopes, and it acts as the teammate who
approved. Because it names its store, `/v1/store/…` needs no store id; the `store_id` in the token response is
there if you want the explicit form (`/v1/stores/st_…/products`). Calls show up in that store's request log under your app's name.

## 4. Refresh

Access tokens last an hour. Refresh before they expire:

```sh
curl -X POST https://api.shopfa.st/v1/oauth/token -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=refresh_token -d refresh_token="sfr_…"
```

Every refresh returns a **new** refresh token and retires the old one. Presenting a retired refresh token again means
someone else has a copy: ShopFast revokes the whole connection and the store has to approve your app again. Store
refresh tokens like passwords and refresh from one place at a time.

## Revoke

`POST /v1/oauth/revoke` with `token` and your client credentials (RFC 7009). Revoking a refresh token ends the
connection; revoking an access token ends just that token. Sellers can disconnect your app from
**Settings → Developers → OAuth apps → Connected apps** at any time.

## Errors

The token endpoint answers RFC 6749 errors: `invalid_request`, `invalid_client` (401), `invalid_grant` (bad, used or
expired code, mismatched `redirect_uri` or `code_verifier`, retired refresh token) and `slow_down` (429).

## Clients without a secret, and MCP

Native, desktop and CLI apps cannot keep a secret. They can **register themselves** and connect with PKCE alone:

```sh
curl -X POST https://api.shopfa.st/v1/oauth/register -H "Content-Type: application/json" \
  -d '{"client_name":"My CLI","redirect_uris":["http://127.0.0.1:53682/callback"],"token_endpoint_auth_method":"none"}'
```

Redirect URIs may be https, http on a loopback address (any port, per RFC 8252), or a private-use scheme such as
`myapp://callback`. Such clients show as **Unverified** on the consent screen, with the address the browser goes back
to. Exchange the code with `client_id` and `code_verifier` only.

Everything a client needs is in the metadata: `https://api.shopfa.st/.well-known/oauth-authorization-server`
(RFC 8414). A client whose `client_id` is an https URL to its own metadata document is registered from it on first use.

To call the [MCP server](https://shopfa.st/docs/ai/mcp), send `resource=https://api.shopfa.st/mcp` (RFC 8707) when you authorize and
exchange the code. Those tokens work on the MCP server only, and the MCP server accepts no other token; AI tools that
speak MCP do all of this for you.
