Skip to main content

Bootstrap a user account

This is the partner / agent flow. Use it when you’re building an AI agent, vertical SaaS, or agency tool that creates Marea accounts for other people. If you already own a Marea storefront and just want API access to it, you don’t need this — grab your mk_user_* from your dashboard and skip straight to Add products.
One call creates the user, a starter storefront, and a per-user API key. Marea also emails a 6-digit code to the user; a follow-up call verifies it and unlocks catalog writes. You never touch the user’s password. The whole flow is two HTTP calls.
# Your own developer key — set this once.
export MAREA_DEV_KEY=mk_dev_xxxxxxxxxxxxxxxx

Step 1 — Create the user

curl -s -X POST https://api.mareaalcalina.com/v1/users \
  -H "Authorization: Bearer $MAREA_DEV_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept-Language: es-MX" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "email": "owner@taqueria.example",
    "displayName": "Marea Taqueria",
    "country": "MX",
    "currency": "MXN",
    "businessType": "restaurant",
    "sourceAgent": "claude-desktop"
  }'
Response — 201 Created:
{
  "userId": "usr_4f1a2b3c4d5e6f7a8b9c0d1e",
  "storefrontId": "stf_9a8b7c6d5e4f3a2b1c0d9e8f",
  "userKey": "mk_user_01HZX9K8QW7VPYR3M2N1B4FJSA",
  "verificationStatus": "pending",
  "verificationExpiresAt": "2026-05-10T18:23:00.000Z",
  "appliedDefaults": { "language": "es", "currency": "MXN", "country": "MX", "businessType": "restaurant" }
}
userKey is shown once. Store it now — there’s no way to read it back. Treat it like a password.
# Save the key returned in the response.
export MAREA_USER_KEY=mk_user_01HZX9K8QW7VPYR3M2N1B4FJSA
export USER_ID=usr_4f1a2b3c4d5e6f7a8b9c0d1e
From here on, every catalog call uses $MAREA_USER_KEY. You only reach back for $MAREA_DEV_KEY when bootstrapping another user, listing your users, or managing webhook endpoints.

Required body fields

FieldRequiredNotes
emailyesWhere the 6-digit code is sent.
displayNameyesShown in the dashboard + on the storefront.
sourceAgentyesYour agent identifier — e.g. claude-desktop. Logged on the user.
country / language / currency / businessTypenoInferred from Accept-Language if omitted; reported back in appliedDefaults.
initialStorefrontnoFull StorefrontManifest if you want products in the same call.

Partial-success (207)

If initialStorefront.products[] exceeds the user’s plan cap, Marea returns 207 with the storefront created up to the cap and an errors[] array describing what was skipped. Surface those verbatim and prompt for upgrade. See Plan limits.

Step 2 — Verify the 6-digit code

The user gets a code in their inbox. Either ask them to read it aloud, or read it via the Gmail MCP — see Verification flow.
curl -s -X POST https://api.mareaalcalina.com/v1/users/$USER_ID/verify \
  -H "Authorization: Bearer $MAREA_USER_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "code": "123456" }'
Response — 200 OK:
{ "userId": "usr_4f1a2b3c4d5e6f7a8b9c0d1e", "verificationStatus": "verified" }
The same $MAREA_USER_KEY is now upgraded in place — it gains catalog:read, catalog:write, storefront:publish on top of the verify scopes it already had. Do not rotate or re-issue. You can now call any product or storefront endpoint.

If the code is wrong / expired / never arrived

# 3/hour, 5/day per user.
curl -s -X POST https://api.mareaalcalina.com/v1/users/$USER_ID/resendVerification \
  -H "Authorization: Bearer $MAREA_USER_KEY"

Two keys, one mental model

You only see two API keys in the whole API:
  • $MAREA_DEV_KEY — your agent’s own key, issued from /developers/keys. Used to bootstrap users and manage webhook endpoints. Never grants catalog access.
  • $MAREA_USER_KEY — a single user’s key, returned by POST /v1/users. Used for everything you do on behalf of that user (catalog reads, product writes, publish) across every storefront the user owns. One key per user, not one key per storefront. Tenant boundary is baked into the key — it can only see/edit that user’s data.
If you hit 403 insufficient_scope, you’re sending the wrong key. The error response includes requiredScopes[] and heldScopes[] so you can tell at a glance which one was needed. Full model + rotation rules: /concepts/keys.

Errors you should branch on

Every non-2xx follows the §9.6 envelope: { type, code, message, doc, param, requestId, requestLogUrl, recoverable, retryAfterMs, nextActions[], upgrade }. Branch on error.type and error.code — never on error.message (localized).

Step 1 — POST /v1/users

HTTPerror.codeWhat happenedAgent action
400invalid_requestEmail malformed, missing field, etc.Fix the body, reissue with a NEW Idempotency-Key.
401missing_authorization / invalid_authorization_format / key_not_found / key_revokedBad/missing/revoked dev keyRe-prompt for a valid mk_dev_*.
403insufficient_scopeDev key lacks developer:bootstrapIssue a key with the right scope (see requiredScopes in body).
409email_existsThe email already has a Marea accountSend the user to log in.
429rate_limit_exceeded (message contains rpd_exceeded)Dev-key daily cap hit (50/day default)Sleep retryAfterMs, then retry.
// 401 — missing header
{
  "error": {
    "type": "auth",
    "code": "missing_authorization",
    "message": "Authorization header is required.",
    "doc": "https://docs.mareaalcalina.com/concepts/keys#authorization",
    "param": "Authorization",
    "requestId": "req_30a9358b-70bd-44f3-aa5d-8983b558ad84",
    "requestLogUrl": "https://mareaalcalina.com/developers/logs/req_30a9358b-70bd-44f3-aa5d-8983b558ad84",
    "recoverable": false,
    "retryAfterMs": null,
    "nextActions": [
      { "label": "Get a developer key.", "method": null, "url": "https://mareaalcalina.com/developers/keys" }
    ],
    "upgrade": null
  }
}
// 409 — email already exists
{
  "error": {
    "type": "conflict",
    "code": "email_exists",
    "message": "An account with this email already exists.",
    "doc": "https://docs.mareaalcalina.com/concepts/errors#email_exists",
    "param": "email",
    "requestId": "req_30a9358b-70bd-44f3-aa5d-8983b558ad84",
    "requestLogUrl": "https://mareaalcalina.com/developers/logs/req_30a9358b-70bd-44f3-aa5d-8983b558ad84",
    "recoverable": false,
    "retryAfterMs": null,
    "nextActions": [
      { "label": "The user already has a Marea account — send them to log in.", "method": null, "url": "https://mareaalcalina.com/login" }
    ],
    "upgrade": null
  }
}
// 429 — dev key hit daily cap
{
  "error": {
    "type": "rate_limited",
    "code": "rate_limit_exceeded",
    "message": "rpd_exceeded — Developer key has reached its daily quota.",
    "doc": "https://docs.mareaalcalina.com/concepts/rate-limits",
    "param": null,
    "requestId": "req_30a9358b-70bd-44f3-aa5d-8983b558ad84",
    "requestLogUrl": "https://mareaalcalina.com/developers/logs/req_30a9358b-70bd-44f3-aa5d-8983b558ad84",
    "recoverable": true,
    "retryAfterMs": 21600000,
    "nextActions": [{ "label": "Wait and retry after the reset.", "method": null, "url": null }],
    "upgrade": null
  }
}

Step 2 — POST /v1/users/:userId/verify

HTTPerror.codeWhat happenedAgent action
400code_invalidWrong digitsAsk the user to re-read — up to 3 attempts before lockout.
404code_not_foundNo active code (already verified or never sent)Call resend, then retry.
404user_not_found:userId doesn’t match the calling keyWrong $MAREA_USER_KEY for this $USER_ID.
410code_expired15-minute TTL elapsedCall resend, then retry.
429too_many_attempts3 failed attempts on the same codeCall resend (issues a new code), then retry.
Full error matrix at /concepts/errors. Idempotency rules at /concepts/safe-mutations. Rate-limit defaults at /concepts/rate-limits.

Next steps