---
title: "Authentication"
description: "Bearer tokens, the team-owned token model, abilities, and the API access gate"
---

> API Reference Index
> Fetch the complete API index at: https://docs.firearmcart.com/api/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> **API access is not included on every plan.** Before you build against it, check
> your store has it: either upgrade to a plan that includes API & Webhooks access,
> or add the **API & Webhooks Access** addon from the **Addons** tab on
> [Billing](/account/billing#addons). Without one, a valid token still gets `403`
> on every `/v1` request and outgoing webhooks stop firing too — see
> [API access gate](#api-access-gate) below.

Every v1 request carries a personal access token in an `Authorization` header:

```bash
curl https://api.firearmcart.com/v1/orders \
  -H "Authorization: Bearer $FIREARMCART_TOKEN" \
  -H "Accept: application/json"
```

There is no OAuth flow, no API key/secret pair, no request signing, and no session cookie. The bearer token is the entire credential.

---

## Creating a token

1. Sign in to `https://app.firearmcart.com`.
2. **Switch to the store you want the token to act on.** The token is bound to whichever store is current when you create it, and this cannot be changed afterwards.
3. Open **Profile → API Tokens** (`/user/api-tokens`).
4. Enter a name, tick the abilities you need, and create.

Six read abilities (`customers:read`, `orders:read`, `fulfillments:read`, `products:read`, `shipping:read`, `taxation:read`) are pre-ticked as a read-only default. At least one ability must be selected — a token with no abilities cannot be created.

### Token lifetime

Tokens **do not expire**. There is no configured expiry and no refresh flow. A token is valid until you delete it in the dashboard, which takes effect immediately.

Store tokens like passwords. A leaked token grants everything its abilities allow, for as long as it exists.

---

## Abilities

Each route is guarded by exactly one ability. A token missing that ability gets `403` even if the token is otherwise valid and the store has API access.

| Ability | Grants | Endpoints |
|---------|--------|-----------|
| `customers:read` | Read customer records and their addresses | `GET /v1/customers`, `GET /v1/customers/{customer_id}` |
| `customers:write` | Create or update a customer by email | `POST /v1/leads` |
| `orders:read` | Read orders, line items, transactions, addresses | `GET /v1/orders`, `GET /v1/orders/{order_id}` |
| `orders:upsell` | Charge an add-on onto an existing paid order | `POST /v1/orders/{order_id}/upsell` |
| `fulfillments:read` | Read fulfillments and their shipments | `GET /v1/fulfillments`, `GET /v1/fulfillments/{fulfillment_id}` |
| `products:read` | Read the catalog, variants and media | `GET /v1/products`, `GET /v1/products/{product_id}` |
| `shipping:read` | Read shipping zones and their rates | `GET /v1/shipping` |
| `taxation:read` | Read manual tax zones | `GET /v1/taxation` |
| `payments:tokenize` | Open a hosted card-entry session | `POST /v1/payments/tokenize` |
| `payments:charge` | Charge a card and create an order | `POST /v1/payments/charge` |
| `subscriptions:read` | Read Reload subscriptions | `GET /v1/subscriptions`, `GET /v1/subscriptions/{uuid}` |
| `subscriptions:write` | Pause, resume, skip, cancel, or edit a subscription | `POST /v1/subscriptions/{uuid}/pause`, `/resume`, `/skip`, `/cancel`, `PATCH /v1/subscriptions/{uuid}` |
| `ffl:read` | Search the FFL dealer directory | `GET /v1/ffl-dealers` |
| `themes:read` | Read theme files over MCP | [Theme Dev MCP server](/api/mcp) only — no v1 REST route |
| `themes:write` | Write theme files over MCP | [Theme Dev MCP server](/api/mcp) only — no v1 REST route |

Fifteen abilities in total. Two of them — `themes:read` and `themes:write` — do **not** unlock any `/v1` endpoint. They gate the [Theme Dev MCP server](/api/mcp) at `https://api.firearmcart.com/mcp/themes`.

That route accepts *any* of the abilities it lists, so a token holding only `themes:read` connects normally. Write access is enforced per tool instead: a read-only token's `tools/list` returns 18 tools rather than 33, because the write tools hide themselves. See [Which ability connects, which ability gates](/api/mcp#which-ability-connects-which-ability-gates) for the exact semantics and [Tools](/api/mcp/tools) for the full surface. That server also has its own rate limit; see [Rate limits](/api/rate-limits#other-buckets).

### Choosing abilities

Grant the narrowest set that works. Two are worth extra thought:

- **`payments:charge`** lets the token move money. Anything holding it can create orders and charge stored cards.
- **`orders:upsell`** does the same on an existing order, and is also the only endpoint that requires you to supply an idempotency key.

---

## API access gate

Passing the token check is not enough. Every `/v1` request also runs an access gate that checks the store's plan and billing standing, and it can reject you with three distinct responses.

| Status | Body | Meaning |
|--------|------|---------|
| `401` | `{"error": "unauthorized", "message": "Invalid API token."}` | The credential authenticated, but did not resolve to a store. Practically: the request reached the API as something other than a store token. |
| `403` | `{"error": "forbidden", "message": "API access is not enabled for this team."}` | The store's plan does not include API & Webhooks access, and it holds no API access add-on. |
| `403` | `{"error": "subscription_expired", "message": "Your subscription has expired. Please renew to continue using the API."}` | The store's subscription is suspended after a failed payment, or is past its cancellation date. |

A store passes the gate if **any** of the following is true: it is a demo account, it has API access granted directly by an administrator, its plan includes API access, or it holds an active API access add-on.

> **Note:** The same gate governs outgoing webhooks. If a store loses API access, queued and future webhook deliveries stop firing as well, even though the webhook rows remain configured. See [Webhooks](/api/webhooks#delivery-is-gated-on-api-access).

Note that this gate is distinct from an invalid or deleted token. A revoked, mistyped or missing token fails earlier, at the authentication guard, and produces `{"message": "Unauthenticated."}` — see [Errors](/api/errors).

---

## Always send `Accept: application/json`

Successful responses are JSON regardless of headers, but **error** responses are not. Without `Accept: application/json`:

- A `401` becomes a `302` redirect to the dashboard login page.
- A `422` validation failure becomes a `302` redirect rather than a JSON error body.
- Other failures render an HTML error page.

Send the header on every request, including `GET`s.

---

## Revoking a token

Delete the token at **Profile → API Tokens**. Revocation is immediate — the next request with that token fails authentication with `401 Unauthenticated`. There is no revocation endpoint; see [What the API does not do](/api/#what-the-api-does-not-do).

If you suspect a leak, delete the token first and issue a replacement second. There is no way to rotate a secret in place.

---

## Related documentation

- [Rate limits](/api/rate-limits) — per-store request budgets
- [Errors](/api/errors) — every failure shape, per status code
- [Theme Dev MCP server](/api/mcp) — the only thing the two `themes:*` abilities unlock
- [Team members](/account/team-members) — who can reach the dashboard page that mints tokens
- [Billing](/account/billing) — plans and add-ons that satisfy the API access gate

Source: https://docs.firearmcart.com/api/authentication/index.mdx
