Errors
Every non-2xx response on this API — except 429, see the note below — is
application/problem+json:
{
"type": "/errors/PRODUCT_NOT_FOUND",
"title": "Not Found",
"status": 404,
"code": "PRODUCT_NOT_FOUND",
"detail": "One or more productIds were not found.",
"instance": "/api/public/v1/runs",
"requestId": "a1b2c3d4-e5f6-4a3b-8c9d-0e1f2a3b4c5d",
"errors": [{ "field": "productIds", "message": "prod_unknown123 does not exist" }]
}
| Field | Always present? | Meaning |
|---|---|---|
type | yes | A stable URI reference identifying the problem type — /errors/{code}. |
title | yes | The HTTP status reason phrase. |
status | yes | The HTTP status code, duplicated in the body per RFC 9457. |
code | yes | The stable machine-readable error code. Branch on this, not on detail or status alone — several codes share a status. |
detail | usually | A human-readable, non-leaky explanation of this specific occurrence. Not a contract — don't parse it. |
instance | usually | The request path this occurrence happened on. |
requestId | yes | The correlation id for this request. Quote it when you ask for help — it's what support uses to find the request in logs. |
errors | on 422 only | One entry per offending field, { field, message }. |
currentStatus | on 409 CREATIVE_NOT_PENDING and CREATIVE_NOT_DELETABLE | The status the creative is actually in, so you can reconcile without another read. |
generatingCount, reason | on 409 CAMPAIGN_NOT_DELETABLE | How many of the campaign's creatives are QUEUED or GENERATING; or reason: "still_being_created" while the campaign is still being created. |
limitKey, limit, current | on 403 LIMIT_EXCEEDED | Which plan limit you hit, what the plan allows, and where you stand. |
Quoting requestId
If a request fails in a way you can't resolve yourself, include requestId when you reach out —
without it, finding the specific request in server-side logs is far slower, and sometimes not
possible at all.
Common codes by status
This is not the full list — every operation's specific error codes are documented on that operation's page in the API reference. These are the ones you'll see across most endpoints.
| Status | Typical codes | What they mean |
|---|---|---|
400 | VALIDATION_ERROR, IDEMPOTENCY_KEY_REQUIRED, INVALID_IDEMPOTENCY_KEY | The request itself is malformed — a bad field shape, a missing required value, a missing or malformed Idempotency-Key. Fix the request; don't retry unchanged. |
401 | (no pinned code) | The token is missing, malformed, unknown, revoked, expired, or the account's plan no longer grants this operation. See Authentication. |
403 | API_TOKEN_SCOPE_DENIED, TIER_REQUIRED, LIMIT_EXCEEDED | The token is valid but lacks the required scope — for example, a read-only key calling approve — or the plan doesn't include the feature (TIER_REQUIRED), or a plan limit is reached (LIMIT_EXCEEDED; a plan limit is a 403, never a 429). See Authentication and Managing campaigns. |
404 | PRODUCT_NOT_FOUND, CREATIVE_NOT_FOUND, ASSET_NOT_FOUND, CAMPAIGN_NOT_FOUND | A referenced resource doesn't exist, doesn't belong to the account/brand this key resolves to, or — on a write — is deleted or has no public representation. All of these get the same answer. |
409 | IDEMPOTENCY_KEY_REUSED, IDEMPOTENCY_IN_PROGRESS, PRICE_EXCEEDS_MAX, PRICE_CHANGED, ACCOUNT_SPEND_CAP_EXCEEDED, CREATIVE_NOT_PENDING, CREATIVE_NOT_DELETABLE, CREATIVE_CAMPAIGN_DELETED, CAMPAIGN_NOT_DELETABLE, CAMPAIGN_CASCADE_PENDING | A conflict with the request's current state — a reused idempotency key, a price that no longer matches what you expected, a spend cap, a creative already reviewed the other way, something busy generating, or a change still finishing. See Idempotency, Try it safely, Reviewing creatives and the write-error table below. |
412 | PRECONDITION_FAILED | The If-Match you sent no longer matches the resource: it changed since you read it. Nothing was changed. Read it again and retry. See Managing creatives. |
422 | VALIDATION_ERROR | A field-level validation failure. Read errors[] for exactly which field(s) to fix. |
429 | RATE_LIMITED | Too many requests in the current window. See the note below and Rate limits. |
503 | ACCOUNT_SPEND_UNAVAILABLE, WRITES_PAUSED, SERVICE_UNAVAILABLE | Temporarily unable to act — carries Retry-After, and is safe to retry with the same Idempotency-Key. ACCOUNT_SPEND_UNAVAILABLE (runs): a dependency needed to answer this request couldn't be reached at that moment — not a statement about your account's balance. WRITES_PAUSED (every write): Mocart has paused API writes for your account or for everyone; nothing changed, and reads are unaffected. See Reviewing creatives. SERVICE_UNAVAILABLE (campaign create or restore): other changes on your account were in flight at once and yours lost the race; nothing was changed. |
Write errors
The write operations add these codes. Branch on code, as always. Each operation's guide lists its own
errors in full: Reviewing creatives, Managing creatives,
Managing assets and Managing campaigns.
| Status | code | What it means | What to do |
|---|---|---|---|
403 | LIMIT_EXCEEDED | Creating or restoring a campaign would put you over your plan's campaign limit, or a campaign over its products limit. limitKey, limit and current say which. | Delete a campaign you no longer need, name fewer products, or upgrade. Don't retry unchanged. |
403 | TIER_REQUIRED | Your plan doesn't include campaigns. | Upgrade the plan. |
409 | CREATIVE_NOT_DELETABLE | The creative is QUEUED or GENERATING and can't be deleted yet. currentStatus says which. | Wait for generation to finish, then retry. |
409 | CREATIVE_CAMPAIGN_DELETED | You restored a creative that was deleted with its campaign, and that campaign is still deleted. | Restore the campaign first. |
409 | CAMPAIGN_NOT_DELETABLE | The campaign holds creatives that are QUEUED or GENERATING (generatingCount), or is still being created (reason). | Wait, then retry. |
409 | CAMPAIGN_CASCADE_PENDING | The campaign's previous delete or restore is still finishing its creatives. Carries Retry-After. | Wait Retry-After, then retry the same request. It succeeds shortly. |
412 | PRECONDITION_FAILED | If-Match no longer matches. Nothing was changed. | Read the resource again and retry with the new ETag. For a campaign, the ETag also changes when its status or counts do, so this can happen without anyone editing it. |
503 | WRITES_PAUSED | Mocart paused API writes for your account or for everyone. Nothing changed and the key wasn't used. | Wait Retry-After, then resend the identical request with the same Idempotency-Key. |
429 isn't application/problem+json everywhere yet
On the Generation plane (GET /presets, every /runs operation), 429 is already
application/problem+json with code: "RATE_LIMITED", like every other error on that plane.
On Catalog, Campaigns & Creatives, and Deliveries, 429 is still the one exception: it returns
a non-JSON body (today, express-rate-limit's own default message) rather than a problem-details
document. This is a known gap on those planes, slated to become application/problem+json after a
90-day deprecation notice (see Versioning) — but until that notice has run its
course, don't parse a 429 body from those planes. Rely on the HTTP status code and the
Retry-After header, which are both stable today on every plane. See
Rate limits.
Every write operation — the creative reviews, and the edits, deletes and restores of creatives, assets and
campaigns — is in the same position: its 429 is the same plain-text body, and every other error it returns
is application/problem+json. Don't parse their 429 body either.