# Authentication

> Publishable keys, secret keys and scopes, cart tokens and customer sessions for the Shoprocket API.

Two API surfaces, two kinds of key. Which one you reach for depends on where your code runs.

## Publishable key vs secret key

| Key | Looks like | Where it runs | What it can do |
| --- | --- | --- | --- |
| Publishable | `pk_...` | Browser, app, any untrusted client | Read the catalogue, build a cart, check out. Identifies the store; proves nothing. |
| Secret | `sk_...` | Your server only | Whatever its scopes allow: products, orders, refunds, webhooks. |

Each store has one publishable key, shown in the dashboard under Settings > General and in every embed snippet. Secret keys are created under Settings > API keys: as many as you like, each with its own name and scopes, each revocable on its own. A secret key is shown once, when you create it; we store only a hash.

Never ship a secret key to the browser

If a secret key reaches client-side code or a public repository, revoke it in Settings > API keys and create a new one. Anyone holding it can do everything its scopes allow.

## Storefront requests

Storefront requests carry the publishable key in the URL path. There is no header to send: the key is public by design, and the storefront API only ever shows what a shopper could see on the shop itself.

They are open to every origin (CORS), so call them straight from the browser.

One storefront door works differently: the Licence API (`/public/licenses/...`) is called by the software you sell, not by a shop page, so it takes no publishable key. Each call carries the product's own `product_secret` in the body instead, shown on the product's Licence keys card.

List products

```
curl https://api-v3.shoprocket.io/v3/public/pk_bCz5dnUr-Mdw6bj0BcoE3CALrGA1vhYq/products
```

## Seller requests

Seller requests send the secret key as a bearer token, and name the store in the path. Your store id starts `str_` and is in the dashboard's address bar: `/stores/str_.../`.

An unknown, revoked or expired key gets `401`. A key used against a store it does not belong to gets `404`, not `403`, so a key cannot be used to discover which store ids exist.

A key can do anything its scopes allow, including registering its own webhook endpoints, which is how an integration subscribes to events. Team management is the exception: it acts on a person, so it takes the dashboard's signed-in session and is not part of the API. See the [reference](https://v3.shoprocket.io/docs/api.md) for the full list.

List orders

```
curl https://api-v3.shoprocket.io/v3/stores/{storeId}/orders \
  -H "Authorization: Bearer sk_..."
```

## Scopes

A key's scopes are the same permissions a team member holds: `view-orders`, `refund-orders`, `edit-products`, `manage-discounts` and so on. Every key needs at least one scope (creating or editing a key with none is refused with `422`), and there is no wildcard: "Full access" in the dashboard simply ticks every scope. Give each integration only what it needs: a stock sync needs `view-products` and `edit-products`, not `refund-orders`.

A call outside a key's scopes gets `403`. You can never create a key with more permission than your own account holds.

## Cart tokens

A shopper's cart is addressed by a **cart token** you mint once per shopper and send as `X-Cart-Token` on every cart call. Any random string up to 64 characters works (a UUID is ideal, as in the example). Mint a fresh one per shopper: a token copied from an example, or shared between shoppers, reaches one shared cart. Keep it in local storage so the cart survives a reload. There is no cart id to track and no session: the same token always reaches the same cart.

Treat it like a session id: anyone holding a shopper's token can read and change that cart.

Read a cart

```
curl https://api-v3.shoprocket.io/v3/public/pk_bCz5dnUr-Mdw6bj0BcoE3CALrGA1vhYq/cart \
  -H "X-Cart-Token: $(uuidgen)"
```

## Signed-in shoppers

Shopper accounts are passwordless. `POST /customer/otp` emails a six-digit code; `POST /customer/otp/verify` exchanges it for a session token. Send that token as `X-Customer-Token` to read the shopper's orders, downloads, licence keys and subscriptions.`POST /customer/signout` revokes it.

Both code endpoints are rate limited per IP address and per email address.

1. Email a code

```
curl -X POST https://api-v3.shoprocket.io/v3/public/pk_bCz5dnUr-Mdw6bj0BcoE3CALrGA1vhYq/customer/otp \
  -H "Content-Type: application/json" \
  -d '{ "email": "shopper@example.com" }'
```

2. Exchange it for a session

```
curl -X POST https://api-v3.shoprocket.io/v3/public/pk_bCz5dnUr-Mdw6bj0BcoE3CALrGA1vhYq/customer/otp/verify \
  -H "Content-Type: application/json" \
  -d '{ "email": "shopper@example.com", "code": "123456" }'
```

## Keeping keys safe

- Keep secret keys in server environment variables, never in source control or client code.
- One key per integration, each with the fewest scopes that work, so revoking one breaks nothing else.
- Revoke a key the moment you suspect it leaked. It stops working immediately.

Next: [API basics](https://v3.shoprocket.io/docs/basics.md) (errors, money, pagination), or the [API reference](https://v3.shoprocket.io/docs/api.md).

---

- HTML version: https://v3.shoprocket.io/docs/authentication
- Language: English (en)
