Skip to main content

AGENTS.md — Marea Alcalina API

Following the agents.md spec. 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.

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

The default credential is mk_user_* — one key per user, covers all storefronts that user owns (1 on Free, up to 5,000 on Agency Growth). The key is owner-scoped, not storefront-scoped. You do NOT issue a separate key per storefront. A second credential, mk_dev_*, exists only for partner-style callers (agents, vertical SaaS, agencies) that bootstrap Marea accounts for other people. Most integrations never hold one. End-user AI clients do not paste a key: they connect over OAuth. When a merchant adds Marea to Claude, ChatGPT, Cursor or any MCP client, the client discovers OAuth 2.1 + PKCE from the WWW-Authenticate challenge on a 401 from POST https://mcp.mareaalcalina.com/mcp (RFC 9728), the merchant signs in to Marea and authorizes. Consent mints a per-user mk_user_* behind the token, so everything below about user-key scope still applies. OAuth grants carry marea.read, marea.write and marea.publish, and can never call marea.bootstrap_user. Static keys stay the path for servers, CI and agents with no browser. See the MCP connect guide. Full model: API keys.
  • mk_user_* — per-user key, owner-scoped (covers all the user’s storefronts). Either minted from the user’s own dashboard, OR returned by POST /v1/users in the partner bootstrap response. Bootstrap-issued keys start restricted (only me:verify + me:resendVerification) until the 6-digit code is verified, then upgraded in-place to catalog:read, catalog:write, storefront:publish. Cannot bootstrap, cannot see other users’ data. Default rpd: 10000.
  • mk_dev_* — partner / developer key, held by the integrator. Holds developer:bootstrap, developer:read, developer:issueUserKey, developer:webhooks. Used to bootstrap users and manage webhook endpoints — never to touch a catalog. Default rpd: 50 (configurable at issue time; the dev rpd IS the bootstrap cap).
Both pass as Authorization: Bearer <key> (or X-API-Key: <key> as a fallback).

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.
  • 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.
  • Branch on error.type, not error.message. The type field is a stable 10-value enum; message is English today and its wording may change. See Errors.
  • Surface nextActions[] verbatim. Errors carry { label, method, url }[] with the concrete actions the user can take (empty when there is nothing to do, for example on 429 or a validation error). 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 connected Marea in their AI client, over OAuth or with a static key: see the MCP connect guide). Ten marea.* tools. There is no delete tool for products, and no delete endpoint either. Hiding is the way: hide: true takes the item off the customer-facing menu and keeps its history. Treat “delete that dish” as a request to hide it, and say so. Prefer the MCP tool when the user has Marea connected: ids, scopes and plan gates are already resolved for that account. Fall back to REST when MCP isn’t available. On an OAuth connection the tool list is already filtered by the granted scopes, so a tool that is absent means the scope was not granted, not that the call would fail.

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. Ask the user in plain language and wait for a yes; never chain it automatically after a create or update. The MCP tool is annotated destructiveHint: true, so most clients also show their own confirmation, but that is the client’s gate, not yours.
  • 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, create a webhook endpoint subscribed to user.verified — see Webhook endpoints.
  • 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 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: Full error matrix at /concepts/errors.

Where to find docs

Conventions

  • Locale: Accept-Language sets the applied defaults on POST /v1/users (language, currency, country). Error messages are English regardless of the header today.
  • 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_).