> ## 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.

# AGENTS

# AGENTS.md — Marea Alcalina API

> Following the [agents.md spec](https://agents.md/). This file is for AI agents (Claude, GPT, Cursor, Continue.dev, generic LLM-driven tools) consuming the Marea public API. For the full doc index in agent-friendly markdown form, fetch [llms.txt](https://docs.mareaalcalina.com/llms.txt).

## What this API is for

Operations + retention API for small-business commerce. Bootstrap users, manage storefronts + products (food menus or retail / service catalogs), publish to public hosted URLs.

End-customer order channels are the operator's choice — WhatsApp, web checkout, or in-person pickup — and configurable per storefront. The API does not lock the channel.

## Auth model

Two-tier (see [Two-tier keys](https://docs.mareaalcalina.com/concepts/keys) for the full model):

* `mk_dev_*` — **developer key**, held by the agent or app builder. Holds `developer:bootstrap`, `developer:read`, `developer:issueUserKey`. Default `rpd: 50` (configurable at issue time; the dev `rpd` IS the bootstrap cap, not a separate quota).
* `mk_user_*` — **per-user key**, returned by `POST /v1/users` and scoped to one user. Issued **restricted** (only `me:verify` + `me:resendVerification`) until the 6-digit code is verified, then upgraded in-place to full catalog scopes (`catalog:read`, `catalog:write`, `storefront:publish`). Cannot bootstrap, cannot see other users' data.

Both pass as `Authorization: Bearer <key>` (or `X-API-Key: <key>` as a fallback). The split lets an agent bootstrap users with its own dev key and receive a per-user key in the response — the agent never holds the user's password.

## Common patterns

* **Bootstrap → verify → use.** `POST /v1/users` returns a *restricted* `mk_user_*` key + emails the user a 6-digit code. The user reads the code aloud (or the agent fetches it via Gmail-MCP), agent calls `POST /v1/users/:userId/verify`, the same key is upgraded to full per-user scope. See [Bootstrap quickstart](https://docs.mareaalcalina.com/quickstart/bootstrap).
* **Idempotency on every mutation.** Pass `Idempotency-Key: <uuid-v4>` on every `POST` and `PATCH`. Same key + same body returns the original response (replay-safe). Same key + different body returns `409 idempotency_conflict`. See [Safe mutations](https://docs.mareaalcalina.com/concepts/safe-mutations).
* **Branch on `error.type`, not `error.message`.** The `type` field is a stable 10-value enum; `message` is localized and may change. See [Errors](https://docs.mareaalcalina.com/concepts/errors).
* **Surface `nextActions[]` verbatim.** Every error returns `{ label, method, url }[]` — these are the concrete actions the user can take. Don't paraphrase; render verbatim.
* **Read rate-limit headers proactively.** `X-RateLimit-Remaining` is on every response. When you hit `429`, read `Retry-After` (seconds) and back off. Defaults: `rpm: 60`. Dev keys: `rpd: 50`. User keys: `rpd: 10000`.
* **Markdown for ingestion.** Every doc page also serves at `<page>.md` (e.g., `/concepts/errors.md`); use that variant when ingesting docs into your context — cleaner than the HTML.

## When to call which tool

REST (always available) and MCP tool (when the user has installed the Marea MCP server in Claude Desktop / Cursor / Continue.dev — see [MCP install quickstart](https://docs.mareaalcalina.com/quickstart/mcp)).

| Goal                                            | REST                                            | MCP tool                                           |
| ----------------------------------------------- | ----------------------------------------------- | -------------------------------------------------- |
| Bootstrap a new user                            | `POST /v1/users` (dev key)                      | `marea.bootstrap_user`                             |
| Verify the 6-digit email code                   | `POST /v1/users/:userId/verify`                 | (use REST in v1)                                   |
| Read identity / plan / budget                   | `GET /v1/me`                                    | `marea.whoami`                                     |
| Create a storefront                             | `POST /v1/storefronts`                          | `marea.create_storefront`                          |
| Update storefront fields                        | `PATCH /v1/storefronts/:id`                     | `marea.update_storefront`                          |
| Add a product                                   | `POST /v1/storefronts/:id/products`             | `marea.create_product`                             |
| Update a product                                | `PATCH /v1/storefronts/:id/products/:productId` | `marea.update_product`                             |
| Publish to public URL (needs user confirmation) | `POST /v1/storefronts/:id/publish`              | `marea.publish_storefront` (fires MCP elicitation) |

Prefer the MCP tool when the user has the MCP server installed — it gives the user explicit confirmation prompts via MCP elicitation primitives. Fall back to REST when MCP isn't available.

## What NOT to do

* **Don't swallow `nextActions[]`.** The most common agent failure mode is silently retrying past a 402 (paywall) or 451 (ToS not accepted) without ever surfacing the action to the user. Render the array verbatim.
* **Don't auto-accept the ToS on the user's behalf.** A 451 → modal flow requires user input by design. The agent cannot bypass.
* **Don't bootstrap past your dev key's `rpd` cap.** Default is 50/day (configurable at issue time). Hitting it returns 429 `rate_limited` with `code: rate_limit_exceeded` and `rpd_exceeded` in `error.message`. Read `Retry-After` (seconds-until-the-day-rolls-over) and back off.
* **Don't call publish without explicit user confirmation.** Publish is destructive and user-visible. Use MCP elicitation if you have the MCP tool; otherwise prompt the user explicitly.
* **Don't cache `mk_user_*` keys across users or sessions.** Per-user keys belong in the user's own context.
* **Don't poll verification status faster than the API allows.** The user-facing rate limit is `rpm: 60` per user-key. Poll at most once every 30s during the verify window; better, subscribe to the `user.verified` agent webhook on the developer key — see [Agent webhooks](https://docs.mareaalcalina.com/concepts/webhooks).
* **Don't reuse the same `Idempotency-Key` with a *different* body.** That returns `409 idempotency_conflict` and you must generate a fresh key. If you're retrying with edits, that's a new request — new key.
* **Don't branch on the localized `error.message`.** Branch on `error.type` (stable enum) or `error.code` (stable string).

## Errors

Every error follows the same envelope: `{ type, code, message, doc, requestId, recoverable, retryAfterMs, nextActions, upgrade, requiredScopes? }`. Branch on `error.type`. The 10 stable types:

| `error.type`                | Typical HTTP | Surface to user?            | Retry?                             | What to do                                                                                                                |
| --------------------------- | ------------ | --------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `rate_limited`              | 429          | No (transparent)            | Yes after `retryAfterMs`           | Sleep + retry same request + same `Idempotency-Key`                                                                       |
| `invalid_request`           | 400 / 410    | No (fix client-side)        | No                                 | Fix the body. Includes `invalid_idempotency_key`, `idempotency_snapshot_unavailable`                                      |
| `auth` (401)                | 401          | Maybe                       | No                                 | Re-prompt for a valid key. Codes: `missing_authorization`, `invalid_authorization_format`, `key_not_found`, `key_revoked` |
| `auth` (insufficient scope) | 403          | Maybe                       | No                                 | Read `requiredScopes` + `heldScopes` arrays; ask user/dev for a key with the right scope                                  |
| `not_found`                 | 404          | Maybe                       | No                                 | Confirm the id. May also be a silent cross-tenant denial if the calling key doesn't own the resource                      |
| `plan_limit`                | 402          | **Yes (paywall)**           | No                                 | Surface `error.upgrade.upgradeUrl` verbatim. The user must upgrade, then retry                                            |
| `internal`                  | 500          | Yes (with `requestId`)      | Once with backoff                  | Then escalate. Surface `requestId` so support can trace                                                                   |
| `conflict`                  | 409          | No                          | Yes after 1s                       | `idempotency_in_flight` — the same key is still processing. Retry with same key                                           |
| `idempotency_conflict`      | 409          | No                          | Yes with **new** `Idempotency-Key` | Same key was used earlier with a different body. Generate a fresh UUID                                                    |
| `service_unavailable`       | 503          | Yes (after retries)         | Yes with backoff                   | Exponential backoff                                                                                                       |
| `tos_not_accepted`          | 451          | **Yes (link to dashboard)** | After user accepts                 | Surface `nextActions[0].url` (modal). Agent cannot bypass                                                                 |

Full error matrix at [/concepts/errors](https://docs.mareaalcalina.com/concepts/errors).

## Where to find docs

* **llms.txt index:** [https://docs.mareaalcalina.com/llms.txt](https://docs.mareaalcalina.com/llms.txt) — agent-curated overview of the entire surface
* **llms-full.txt:** [https://docs.mareaalcalina.com/llms-full.txt](https://docs.mareaalcalina.com/llms-full.txt) — concatenation of all pages (Mintlify auto-emits in production)
* **Per-page markdown:** `https://docs.mareaalcalina.com/<page>.md` (e.g., `/concepts/errors.md`)
* **OpenAPI 3.1 spec:** [https://api.mareaalcalina.com/v1/openapi.json](https://api.mareaalcalina.com/v1/openapi.json) — Zod-derived from the cloud-functions code; 5-min cached
* **Postman collection:** [https://docs.mareaalcalina.com/marea.postman.json](https://docs.mareaalcalina.com/marea.postman.json) — auto-generated from the OpenAPI spec
* **MCP server:** [https://mcp.mareaalcalina.com](https://mcp.mareaalcalina.com) — paste-token install for Claude Desktop / Cursor / Continue.dev; 7 `marea.*` tools (verify + resend are deliberately REST-only)
* **API catalog (RFC 9727):** [https://mareaalcalina.com/.well-known/api-catalog](https://mareaalcalina.com/.well-known/api-catalog) — discoverability for agent crawlers

## Conventions

* **Locale:** `Accept-Language` is honored. Default `es-MX`. Pass `Accept-Language: en` for English error messages, `pt` for Portuguese.
* **Currency:** configurable per storefront. Pass `currency: "MXN" | "USD" | "CAD" | "BRL" | ...` on storefront create.
* **Phone format:** E.164 (`+52...`).
* **Timestamps:** ISO 8601 strings.
* **IDs:** prefixed (`usr_`, `stf_`, `prd_`, `dev_`).
