Skip to main content

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.

Add and edit products

Once a user is verified (Bootstrap) and you hold their mk_user_* key, you can add and edit products on any storefront they own. Both endpoints require scope catalog:write and honor Idempotency-Key (see Safe mutations).

Add a product

POST /v1/storefronts/{storefrontId}/products — returns 201 Created with the full product DTO.
curl -X POST https://api.mareaalcalina.com/v1/storefronts/stf_9a8b7c6d/products \
  -H "Authorization: Bearer mk_user_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Accept-Language: es-MX" \
  -H "Idempotency-Key: 2f1a8c4b-2e3a-4b9d-9f1a-8c4b2e3a4b9d" \
  -d '{
    "title": "Taco al pastor",
    "price": 25,
    "imageUrl": "https://example.com/pastor.jpg",
    "category": "Tacos",
    "description": "Marinated pork, pineapple, cilantro, onion."
  }'

Response — 201 Created

{
  "product": {
    "id": "prd_8c4b2e3a4b9d9f1a8c4b2e3a",
    "title": "Taco al pastor",
    "description": "Marinated pork, pineapple, cilantro, onion.",
    "price": 25,
    "salePrice": null,
    "category": "Tacos",
    "subcategory": null,
    "imageUrl": "https://example.com/pastor.jpg",
    "thumbnailUrl": null,
    "sku": null,
    "slug": null,
    "position": 12,
    "cartProduct": null,
    "hide": null,
    "stock": null,
    "tags": null,
    "extraProductsCategory": null,
    "imageProcessingPending": true,
    "createdAt": "2026-05-10T18:25:00.000Z",
    "updatedAt": "2026-05-10T18:25:00.000Z"
  }
}
imageProcessingPending: true means Marea will sweep the source URL into its own CDN asynchronously; imageUrl continues to serve from the source until then.

Required + optional fields

FieldTypeRequiredNotes
titlestring (1–200)yes
pricenumber (≥ 0)yesStorefront currency.
descriptionstringno
salePricenumber (≥ 0)noIf set + below price, shown crossed-out.
category / subcategorystringno
imageUrl / thumbnailUrlURLnoBoth swept into Marea CDN async (BL-CAT-10).
sku / slugstringno
positionintnoDefaults to last + 1.
cartProduct / hidebooleanno
stockintnoSurfaced on the storefront as inventory remaining.
tagsstring[]no
extraProductsCategoryobject[]noModifier groups (size, toppings). See OpenAPI schema.

Update a product

PATCH /v1/storefronts/{storefrontId}/products/{productId} — returns 200 OK with the full updated ProductDto. Every field is PATCHable (no immutable fields).
curl -X PATCH https://api.mareaalcalina.com/v1/storefronts/stf_9a8b7c6d/products/prd_8c4b2e3a \
  -H "Authorization: Bearer mk_user_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 1b2c3d4e-5f6a-7b8c-9d0e-1f2a3b4c5d6e" \
  -d '{ "price": 28, "salePrice": 25 }'
Partial update — only the fields you send change. To clear an optional field, send null (e.g. { "imageUrl": null }). Omitting a field leaves it untouched.

Idempotency

Same Idempotency-Key + same body within 24 hours → original response replayed (no duplicate writes). Same key + different body → 409 idempotency_conflict. See Safe mutations. If your batch importer hits a transient network error mid-flight, retry with the same Idempotency-Key per product — the server is replay-safe.

Plan caps and 402 mid-batch

The single-product POST endpoint returns 402 plan_limit (plan_max_products_reached) the moment a write would exceed the user’s plan cap. Stop the batch as soon as you see it; surface error.upgrade.upgradeUrl; do not retry until the user upgrades.
// 402 — adding this product would exceed the plan cap
{
  "error": {
    "type": "plan_limit",
    "code": "plan_max_products_reached",
    "message": "Plan limit of 30 products reached. Upgrade to add more.",
    "doc": "https://docs.mareaalcalina.com/concepts/plan-limits#products",
    "param": "products",
    "requestId": "req_...",
    "requestLogUrl": "https://mareaalcalina.com/developers/logs/req_...",
    "recoverable": true,
    "retryAfterMs": null,
    "nextActions": [
      { "label": "Upgrade the plan to add more products.", "method": null, "url": "https://mareaalcalina.com/upgrade?planSource=api" }
    ],
    "upgrade": {
      "currentPlan": "free",
      "requiredPlan": "basic",
      "upgradeUrl": "https://mareaalcalina.com/upgrade?planSource=api"
    }
  }
}

Bulk-load alternative: 207 Multi-Status

If you’re seeding a whole catalog at once, use POST /v1/storefronts with the manifest’s products[] array (or pass initialStorefront.products on POST /v1/users). When the manifest exceeds the plan cap, Marea returns 207 Multi-Status — the storefront is created with products up to the cap and the response errors[] array lists what was skipped:
{
  "storefront": { "id": "stf_...", "name": "Tacos La Marea", "published": false },
  "errors": [
    { "type": "plan_limit", "code": "products_over_limit", "message": "Skipped 'Taco al pastor': free tier allows 30 products.", "details": { "skippedCount": 5, "skippedProducts": [/* ... */] } }
  ]
}
Surface the skipped list to the user and offer the upgrade; the accepted products are already live.

Other recoverable errors

// 404 — wrong storefrontId/productId, or cross-tenant (leak-less)
{
  "error": {
    "type": "not_found",
    "code": "storefront_not_found",
    "message": "Storefront not found.",
    "doc": "https://docs.mareaalcalina.com/concepts/errors#not_found",
    "param": null,
    "requestId": "req_...",
    "requestLogUrl": "https://mareaalcalina.com/developers/logs/req_...",
    "recoverable": false,
    "retryAfterMs": null,
    "nextActions": [],
    "upgrade": null
  }
}
// 400 — body validation (e.g. negative price)
{
  "error": {
    "type": "invalid_request",
    "code": "invalid_request_body",
    "message": "price must be ≥ 0.",
    "doc": "https://docs.mareaalcalina.com/concepts/errors",
    "param": "price",
    "requestId": "req_...",
    "requestLogUrl": "https://mareaalcalina.com/developers/logs/req_...",
    "recoverable": true,
    "retryAfterMs": null,
    "nextActions": [
      { "label": "Fix the body, then retry with a NEW Idempotency-Key.", "method": null, "url": null }
    ],
    "upgrade": null
  }
}
Quick lookup:
HTTPerror.type / codeWhat happenedAgent action
400invalid_request / invalid_request_bodyBody validation (negative price, missing title, malformed field) — see error.param for which field failedFix the body, reissue with a NEW Idempotency-Key.
402plan_limit / plan_max_products_reachedThis single-product write would exceed the plan capSurface error.upgrade.upgradeUrl; do not retry until upgrade.
404not_found / storefront_not_foundWrong storefront id, or this user-key doesn’t own it (silent cross-tenant). Product not-found also surfaces here.Confirm the ids; cannot infer ownership.
409idempotency_conflictSame Idempotency-Key reused with a different bodyGenerate a fresh UUID for the retry.
429rate_limited / rate_limit_exceeded (message contains rpm_exceeded)Per-user rpm: 60 capSleep Retry-After (seconds), retry with the same key.

Cross-references