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

# Authentication

> Authenticate the API and MCP server: connect an agent with OAuth (no key), or use a workspace API key, with bearer mk_live_ tokens, one key per workspace, and a symmetric read/write/delete scope per resource.

The Merchkit REST API authenticates with a workspace API key. Create a key in your workspace
settings and send it as a bearer token on every request. The MCP server accepts the same key. It
also lets an agent connect over OAuth with no key at all, the recommended path for tools like
Claude, ChatGPT, and VS Code. See [connecting an agent](#connecting-an-agent) for that flow; the
rest of this page covers API keys and the scopes they carry.

<Note>
  The examples use `https://www.merchkit.com` as the base URL.
</Note>

## API keys

Send your key in the `Authorization` header:

```http theme={null}
Authorization: Bearer mk_live_...
```

<CodeGroup>
  ```bash cURL theme={null}
  curl https://www.merchkit.com/api/v1/products \
    -H "Authorization: Bearer mk_live_..."
  ```

  ```ts fetch theme={null}
  const res = await fetch("https://www.merchkit.com/api/v1/products", {
    headers: { Authorization: "Bearer mk_live_..." },
  });
  ```
</CodeGroup>

<Callout type="warning">
  A key is a secret with workspace access. Store it in a secret manager or environment variable.
  Never commit it to source control or expose it to a browser client.
</Callout>

### One key, one workspace

Each API key maps to exactly one workspace. No account-level key spans workspaces. To operate across
multiple workspaces, create one key per workspace and select the right key per request. The same key
authenticates both the REST API and the [MCP server](/developers/mcp).

## Connecting an agent

Interactive agents don't need an API key. When you add Merchkit to Claude, ChatGPT, VS Code, or
another MCP client, the app connects to the [MCP server](/developers/mcp) over OAuth 2.1: you sign
in, pick the workspace to connect, and choose the scopes to grant on a consent screen.

* No secret sits on disk. The agent holds a short-lived token, not your API key.
* One connection maps to one workspace, the same model as an API key.
* Scopes work the same way. The consent screen grants the same scopes documented below, with every
  scope except the `delete:*` scopes selected by default.
* Revoke access anytime under **Settings → API keys → Connected apps**. Revoking cuts the agent's
  access on its next request.

Use an API key for non-interactive clients that can't complete a browser sign-in: servers, scripts,
and CI. See the [MCP guide](/developers/mcp#connect-in-one-click) for the one-click connect buttons.

## Scopes

Keys carry **scopes** that grant least-privilege access. Each operation declares the scope(s) it
requires, and the key must hold all of them. Otherwise the request fails with
[`insufficient_scope`](/developers/errors#insufficient_scope). Every entity resource gets a
symmetric read / write / delete triple. Attribute values are governed by the owning resource's
write scope: writing a product's `price` needs `write:products`, and an image's `alt_text` needs
`write:images`.

| Scope group           | Scopes                                                       | Grants                                                                       |
| --------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- |
| Products              | `read:products` / `write:products` / `delete:products`       | Products and variants, references, per-channel completeness                  |
| Images                | `read:images` / `write:images` / `delete:images`             | Images (their own triple — no longer piggybacking product scopes)            |
| Vendors               | `read:vendors` / `write:vendors` / `delete:vendors`          | Vendors                                                                      |
| Categories            | `read:categories` / `write:categories` / `delete:categories` | Categories                                                                   |
| Sources               | `read:sources` / `write:sources` / `delete:sources`          | Data sources, including scraped content; `write:sources` triggers processing |
| Attributes            | `read:attributes` / `write:attributes`                       | Attribute definitions everywhere, plus attribute classes over MCP            |
| Grid views (MCP only) | `read:views` / `write:views`                                 | Saved grid views — an MCP-only surface, no REST endpoints                    |
| Channels              | `read:channels`                                              | The enabled channel catalog (read-only)                                      |
| Events                | `read:events`                                                | The workspace change feed **and** per-entity events                          |
| Jobs                  | `read:jobs`                                                  | Listing and polling async jobs                                               |

These scopes no longer exist:

* `write:attribute_values` is gone. Value writes use the resource's own write scope on REST and MCP
  alike. The old split, where REST checked `write:products` while MCP checked
  `write:attribute_values`, no longer applies.
* `read:completeness` is gone. Completeness is an ordinary product read (`read:products`).
* `read:history` is renamed to `read:events`.

<Callout type="info">
  Already issued keys were remapped automatically to this taxonomy. `read:history` became
  `read:events`. Keys holding product scopes gained the matching `images` scopes, since images
  shared product scopes before. `write:attribute_values` became the four entity write scopes it
  effectively granted. Review your keys and trim anything broader than the integration needs. The
  `sources` scopes are net-new: no old key holds them, so grant them explicitly.
</Callout>

<Callout type="info">
  The OpenAPI spec documents the required scope for every operation in its `securitySchemes`, so an
  agent can determine up front whether a key can perform an action.
</Callout>

### Read-only vs write keys

Because scopes are per-resource with split read/write/delete, you can mint keys for different jobs:

* A read-only key holds only `read:*` scopes. It can list and read every resource you authorize but
  cannot mutate anything. Use it for dashboards, analytics, sync-out jobs, or pointing an agent at
  your catalog for inspection.
* A write key adds the `write:*` scopes the integration needs to those reads. Use it for enrichment,
  imports, upserts, and any agent that mutates data.

A read-only key cannot perform writes. Scope enforces this on both the REST API and the MCP write
tools.

## Beta limitation: deletes are disabled

<Callout type="warning">
  During the beta, destructive operations are disabled. `DELETE` endpoints return `403`, and there
  is no delete tool in MCP, even if a key holds a `delete:*` scope. Destructive actions are enabled
  in a later release. Build your integrations against create, update, and upsert for now.
</Callout>
