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.
Errors
Every non-2xx response uses the same envelope. Agents branch on error.type (a stable 10-value enum); each error carries nextActions[] — concrete steps to surface to the user.
Envelope shape
{
"error": {
"type": "rate_limited",
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded (rpm_exceeded). Retry after 23s.",
"doc": "https://docs.mareaalcalina.com/concepts/rate-limits",
"param": null,
"requestId": "req_30a9358b-70bd-44f3-aa5d-8983b558ad84",
"requestLogUrl": "https://...",
"recoverable": true,
"retryAfterMs": 23000,
"nextActions": [
{ "label": "Wait 23s and retry the same request.", "method": null, "url": null }
],
"upgrade": null
}
}
Field guarantees
| Field | Type | Notes |
|---|
type | enum (10 values) | Stable. Agents branch on this. See the table below. |
code | string | Stable, machine-readable. Finer-grained than type. |
message | string | Human-readable. Localized via Accept-Language. Do not branch on it. |
doc | string | Link to the relevant docs page for this code. |
param | string | null | Name of the offending field (null if not field-specific). Never missing. |
requestId | string | req_<uuid> — surface to the user when reporting bugs. |
requestLogUrl | string | Internal log URL (operator-only). |
recoverable | boolean | true means a retry is meaningful (with appropriate changes). |
retryAfterMs | integer | null | When set, also returned as the Retry-After header (in seconds). |
nextActions | array | Always an array (possibly empty). null is not allowed. Each entry: { label, method, url }. |
upgrade | object | null | { currentPlan, requiredPlan, upgradeUrl, previewUrl? }. Set only on plan_limit errors. |
requiredScopes | string[] (optional) | Present only when the failure is a scope mismatch. |
heldScopes | string[] (optional) | Present only when the failure is a scope mismatch. |
The 10 error types
type | Typical HTTP | When it happens | Agent action |
|---|
rate_limited | 429 | rpm or rpd quota exceeded | Read retryAfterMs, sleep, retry. recoverable: true. See Rate limits. |
invalid_request | 400 / 410 / 422 | Malformed body, invalid Idempotency-Key, oversize snapshot, empty publish | Fix the body and reissue. Do not retry the exact same request. |
auth | 401 / 403 | Missing / invalid / revoked key, missing scope | 401 → fix credential. 403 with requiredScopes → request an upscoped key. |
not_found | 404 | Resource missing OR cross-tenant access (silent denial) | Confirm the id is correct AND the key owns it. |
plan_limit | 402 | Plan ceiling hit (publish blocked, product cap exceeded) | Surface upgrade.upgradeUrl to the user as a CTA. See Plan limits. |
internal | 500 | Server bug | Surface requestId. Retry once with backoff, then escalate. |
conflict | 409 | Idempotency-Key in flight (same key still processing) | Wait retryAfterMs and retry with the same key. |
idempotency_conflict | 409 | Same Idempotency-Key reused with a different body | Generate a NEW Idempotency-Key. See Safe mutations. |
service_unavailable | 503 | Dependency outage or feature flag off | Retry with exponential backoff. |
tos_not_accepted | 451 | Calling user has not accepted the Terms of Service | Surface nextActions[0] (dashboard link). User must accept; agent cannot bypass. See ToS jurisdiction. |
Recovery matrix
If error.type is | Surface to user? | Retry? | What to do |
|---|
rate_limited | No (transparent) | Yes after retryAfterMs | Sleep + retry same request + same Idempotency-Key |
invalid_request | No (fix client-side) | No (without fixing) | Fix the body. Do not retry blindly. |
auth (401) | Maybe (credential issue) | No | Re-prompt for a valid key |
auth (403, scope) | Maybe | No | Request a key with requiredScopes |
not_found | Maybe (“not found”) | No | Confirm id; confirm key ownership |
plan_limit | Yes (paywall CTA) | No | Surface upgrade.upgradeUrl verbatim |
internal | Yes (with requestId) | Once with backoff | Then escalate |
conflict (in-flight idempotency) | No | Yes after 1s | Retry with same Idempotency-Key |
idempotency_conflict | No | Yes with NEW key | Generate a fresh Idempotency-Key |
service_unavailable | Yes (after retries) | Yes with backoff | Exponential backoff |
tos_not_accepted | Yes (ToS link) | After user accepts | Surface nextActions[0].url |
Stable code reference
Every code below is emitted by production code today. Agents may branch on code for finer-grained handling than type.
Auth (401 / 403)
code | HTTP | Source |
|---|
missing_authorization | 401 | src/api/middleware/apiKey.ts |
invalid_authorization_format | 401 | src/api/middleware/apiKey.ts |
key_not_found | 401 | src/api/middleware/apiKey.ts |
key_revoked | 401 | src/api/middleware/apiKey.ts |
insufficient_scope | 403 | src/api/middleware/scope.ts — includes requiredScopes/heldScopes |
developer_context_unresolved | 401 | Bootstrap / issue-key handlers when developer ctx is missing |
tenant_unresolved | 401 | Catalog / verify handlers when owner ctx is missing |
developer_not_found | 404 | src/api/v1/me.get.ts — dev account missing from Firebase Auth |
Validation (400 / 410 / 413 / 422)
code | HTTP | Source |
|---|
invalid_request | 400 | Zod validation failure (per-field) |
invalid_json | 400 | Body parsing failure |
invalid_idempotency_key | 400 | src/api/middleware/idempotency.ts |
invalid_storefront_id | 400 | src/api/services/translation/storefront-id.ts |
invalid_product_id | 400 | src/api/services/translation/storefront-id.ts |
invalid_email_syntax | 400 | Bootstrap email validation |
invalid_email_mx | 400 | Bootstrap MX-record check |
code_invalid | 400 | src/api/v1/users.verify.ts |
code_expired | 410 | src/api/v1/users.verify.ts |
idempotency_snapshot_unavailable | 410 | src/api/middleware/idempotency.ts (oversize replay) |
payload_too_large | 413 | Body parser |
no_products | 422 | src/api/v1/storefronts.publish.ts (empty storefront) |
user_not_verified | 422 | src/api/v1/users.issueKey.ts (pre-verify additional key) |
blocks_explicit_not_supported | 422 | Storefront manifest (legacy block fields) |
theme_preset_not_supported | 422 | Storefront manifest (theme presets not on plan) |
Not found (404)
code | HTTP | Source |
|---|
storefront_not_found | 404 | src/api/v1/storefronts.* — also fires on cross-tenant access |
user_not_found | 404 | Verify, resend, issue-key handlers — also cross-tenant |
code_not_found | 404 | src/api/v1/users.verify.ts — no active verification code |
Conflicts (409)
code | HTTP | Source |
|---|
idempotency_in_flight | 409 | src/api/middleware/idempotency.ts — emits Retry-After: 1 |
idempotency_conflict | 409 | src/api/middleware/idempotency.ts — different body, same key |
email_exists | 409 | Bootstrap — email already registered |
Plan / paywall (402)
code | HTTP | Source |
|---|
plan_blocks_publish | 402 | src/api/v1/storefronts.publish.ts (NO_ACTIVO plan) |
plan_max_products_reached | 402 | src/services/catalog/productCreateCore.ts — single-product POST would exceed the plan cap |
plan_max_storefronts_reached | 402 | src/api/v1/storefronts.create.ts — would exceed the per-plan storefront cap |
plan_limit_exceeded | 402 | src/api/middleware/planLimits.ts — generic plan-cap rejection at middleware layer |
products_over_limit | 207/402 | src/api/v1/storefronts.create.ts (manifest with too many products) — 207 partial OR 402 over cap |
Rate limit (429)
code | HTTP | Source |
|---|
rate_limit_exceeded | 429 | src/api/middleware/rateLimit.ts — rpm_exceeded / rpd_exceeded |
too_many_attempts | 429 | src/api/v1/users.verify.ts — 3 wrong codes |
bootstrap_ip_rate_limited | 429 | src/api/services/bootstrap.service.ts |
bootstrap_quota_exhausted | 429 | src/api/services/bootstrap.service.ts |
resend_hour_limit | 429 | src/api/v1/users.resendVerification.ts |
resend_day_limit | 429 | src/api/v1/users.resendVerification.ts |
ToS (451)
code | HTTP | Source |
|---|
tos_required | 451 | Reserved on storefronts.publish — see ToS jurisdiction |
Service / internal (500 / 503)
code | HTTP | Source |
|---|
api_disabled | 503 | src/api/middleware/featureFlag.ts — kill switch |
verify_unexpected_state | 500 | src/api/v1/users.verify.ts — defense-in-depth fall-through |
internal_error | 500 | Unhandled exception path |
What NOT to do
- Do not branch on
error.message — it’s localized via Accept-Language and may change. Branch on error.type (enum) and error.code (string).
- Do not swallow
nextActions[]. The most common agent-failure mode is silently retrying past a 402 (paywall) or 451 (ToS) without ever showing the user the action.
- Do not auto-accept the ToS on the user’s behalf. The 451 → modal flow requires user input by design.
- Do not retry
invalid_request with the same body. Fix the body first.
- Do not branch on HTTP status alone. Several types share a status (409 for two conflict shapes; 404 for both real-missing and cross-tenant). The
type + code pair disambiguates.
Verification in code
src/api/contracts/error.zod.ts — the locked envelope schema (10 type values).
src/api/api-error.ts — the ApiError class that wires status + type + code + recovery hints.
src/api/middleware/errors.ts — the global error handler that emits the envelope.