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

# API keys

> User-key is the default for almost everyone. Developer-key exists for partners bootstrapping users on others' behalf.

# API keys

Almost every integration with Marea uses **one kind of key**: a `mk_user_*` issued to a single Marea user, used to manage **all** of that user's storefronts and products.

A second kind — `mk_dev_*` — exists for a separate class of caller: agents, vertical-SaaS, and agencies that create Marea accounts on behalf of *other* people. Most readers will never need one.

```http theme={null}
GET /v1/me HTTP/1.1
Host: api.mareaalcalina.com
Authorization: Bearer mk_user_xxxxxxxxxxxxxxxx
```

Format is locked: `^mk_(dev|user)_[A-Za-z0-9]+$`. The random suffix is 24 base62 chars (\~143 bits of entropy).

## The default: `mk_user_*` (user-key)

A user-key represents one specific Marea user and is the credential you use for everything that happens *inside* that user's account.

<Note>
  **One key covers ALL of that user's storefronts.** A user-key is **owner-scoped, not storefront-scoped**. The same `mk_user_*` can `GET` / `POST` / `PATCH` / `publish` against every storefront the user owns — whether that's 1 (Free plan) or 5,000 (Agency Growth plan). You do *not* issue a separate key per storefront. You do *not* pass a `Storefront-Id` header — the storefront is in the URL path; the key proves you own it.
</Note>

| Holds                                                 | Default scopes                                                       |
| ----------------------------------------------------- | -------------------------------------------------------------------- |
| One specific end-user (and **all** their storefronts) | `catalog:read`, `catalog:write`, `storefront:publish` (after verify) |

**How to get one:**

* If you signed up at [mareaalcalina.com](https://mareaalcalina.com) and own a storefront, mint a `mk_user_*` in one click from your dashboard at [`/developers/keys`](https://mareaalcalina.com/developers/keys).
* If you were bootstrapped through a partner integration, you received your `mk_user_*` in the `POST /v1/users` response — store it then.

**What you can do with it:**

* `GET /v1/me` — confirm identity, read your plan + limits
* `GET /v1/storefronts`, `POST /v1/storefronts`, `PATCH /v1/storefronts/:id` — manage your storefronts
* `POST .../products`, `PATCH .../products/:id`, `GET .../products` — manage your catalog
* `POST .../publish` — take a storefront live (gated by ToS + plan)
* *(after Phase B)* `POST /v1/webhook_endpoints` — receive `order.*` events from your store

**What you can't do:**

* Bootstrap other users (that requires `developer:bootstrap`, held only by `mk_dev_*`)
* See or edit anyone else's data — tenant boundary is encoded in the key itself

### Pre-verify vs post-verify scopes

If your `mk_user_*` came from a fresh `POST /v1/users` (partner bootstrap), it starts with a **restricted** scope set — `me:verify`, `me:resendVerification`, `catalog:read`. The only mutating calls it can make are verify and resend.

When the user submits the 6-digit code (`POST /v1/users/:userId/verify`), the **same key** is upgraded in place to `catalog:read`, `catalog:write`, `storefront:publish`. No rotation — store the value once at bootstrap time.

User-keys minted from the dashboard (self-issued by an already-verified user) start in the full post-verify state.

## The advanced case: `mk_dev_*` (partner / developer key)

A `mk_dev_*` is for callers that act on behalf of *many* users — three real archetypes:

1. **AI agents** (Claude, ChatGPT, Cursor) that bootstrap Marea accounts as part of helping their own end-users.
2. **Vertical SaaS** (e.g., a LATAM restaurant-management product) embedding Marea so their customers get a storefront without the SaaS building one.
3. **Agencies** running marketing for many small clients — one operator console, many client storefronts.

If none of those describe you, skip this section.

| Holds                          | Default scopes                                                                          |
| ------------------------------ | --------------------------------------------------------------------------------------- |
| The partner integration itself | `developer:bootstrap`, `developer:read`, `developer:issueUserKey`, `developer:webhooks` |

**How to get one:** Issue from the developer dashboard at [mareaalcalina.com/developers/keys](https://mareaalcalina.com/developers/keys). The raw value is shown once; server-side only the SHA-256 hash is stored.

**What you can do with it:**

* `POST /v1/users` — bootstrap a new Marea user (returns their `mk_user_*` in the response)
* `GET /v1/users`, `GET /v1/users/:userId` — list / inspect users you've bootstrapped
* `POST /v1/users/:userId/keys` — mint an additional `mk_user_*` for a user you've bootstrapped (e.g., a second integration)
* All `/v1/webhook_endpoints/*` operations — manage your webhook endpoints (`user.*` lifecycle on accounts you bootstrapped + `order.*` from those accounts' storefronts)

**What you can't do:**

* Touch any user's catalog directly. To edit a user's storefront, use the `mk_user_*` you got back from their bootstrap. Even a partner who owns a Marea storefront themselves uses *their own* `mk_user_*` for that storefront — the dev-key never grants catalog access.

The split is a security boundary: a leaked `mk_dev_*` can bootstrap new fake accounts and read user metadata, but can't rewrite a single user's prices.

## Authenticating

Pass the key in `Authorization: Bearer <key>` (preferred). `X-API-Key: <key>` is accepted as a fallback. Any other header — or a non-`Bearer` `Authorization` scheme — fails with `invalid_authorization_format`.

## Scopes table

Scope checks run after key lookup. Endpoints declare a required scope set; the auth layer supports both `any` (one-of) and `all` (require-all) semantics.

| Scope                    | Held by                           | Used by                                                                |
| ------------------------ | --------------------------------- | ---------------------------------------------------------------------- |
| `catalog:read`           | user keys                         | `GET /v1/storefronts/...`, `GET .../products`                          |
| `catalog:write`          | user keys                         | `POST` / `PATCH` on storefronts and products                           |
| `storefront:publish`     | user keys                         | `POST /v1/storefronts/:storefrontId/publish`                           |
| `me:verify`              | restricted user keys (pre-verify) | `POST /v1/users/:userId/verify`                                        |
| `me:resendVerification`  | restricted user keys              | `POST /v1/users/:userId/resendVerification`                            |
| `developer:bootstrap`    | dev keys                          | `POST /v1/users`                                                       |
| `developer:read`         | dev keys                          | `GET /v1/users`, `GET /v1/users/:userId`                               |
| `developer:issueUserKey` | dev keys                          | `POST /v1/users/:userId/keys`                                          |
| `developer:webhooks`     | dev keys                          | `POST /v1/webhook_endpoints` + the rest of `/v1/webhook_endpoints/...` |

`GET /v1/me` accepts any of the user scopes; developer keys bypass the scope check entirely (a developer key always sees its own identity).

### Scope mismatch (403)

```json theme={null}
{
  "error": {
    "type": "auth",
    "code": "insufficient_scope",
    "message": "Missing required scopes: catalog:write.",
    "requiredScopes": ["catalog:write"],
    "heldScopes": ["developer:bootstrap"],
    "recoverable": false
  }
}
```

Surface `requiredScopes` and `heldScopes` verbatim — the diff between them tells you exactly which kind of key you should have used (`mk_user_*` for catalog scopes; `mk_dev_*` for developer scopes).

## Rotation

There is no `rotate` call. To rotate:

1. Revoke the old key.
2. Issue a new one (developer dashboard for `mk_dev_*`; user dashboard for `mk_user_*`; or `POST /v1/users/:userId/keys` with a developer key for an additional user-key on a bootstrapped user).
3. Update your client.

## Revocation

Revoked keys stop authenticating within \~60s end-to-end (the negative-lookup cache TTL). After revocation:

* The key returns `401 key_revoked` on every request.
* Webhook endpoints are NOT tied to a single key — they belong to your account and survive key revocation. Revoking a single key does not delete or disable any endpoint. See [Webhook endpoints](/concepts/webhooks) for the lifecycle.
* Revocation is **non-cascading**: revoking a developer key does NOT revoke the user keys it minted. To wipe everything for an owner, the owner-scoped revoke-all path is used (operator path; not in the public API).

## Auth errors (401)

| `error.code`                   | When                                            | Recovery        |                |
| ------------------------------ | ----------------------------------------------- | --------------- | -------------- |
| `missing_authorization`        | Neither `Authorization` nor `X-API-Key` present | Add the header  |                |
| `invalid_authorization_format` | Header present but not \`Bearer mk\_(dev        | user)\_...\`    | Fix the format |
| `key_not_found`                | Key not recognized (typo or never issued)       | Issue a new key |                |
| `key_revoked`                  | Key was issued but later revoked                | Issue a new key |                |

All four are 401 with `recoverable: false`. Don't loop — fix the credential.
