Skip to main content

Changelog

Every entry names what changed and, for a breaking change, the announcement and sunset dates. See Versioning for what "breaking" means here and how the notice period works.

2026-10-04 — Image upscale (API 1.9.0)​

Additive. Two new operations re-render an image you own at 2K or 4K as a new asset, on the same runs:write / runs:read scopes and rate limits as runs:

  • POST /upscales (createImageUpscale) starts an upscale from { "source": { "assetId" } | { "productId", "image", "index"? }, "target": "2K" | "4K" }. Idempotency-Key is required; the answer is 202 with the upscale and a Location header. Credits are held when it is accepted and charged only for a delivered image.
  • GET /upscales/{upscaleId} (getImageUpscale) reads it: status is QUEUED, PROCESSING, SUCCEEDED or FAILED.

An Asset made this way carries one new optional key, derivation ({ kind: "ai_rerender", operation: "image_upscale", parentAssetId, rootAssetId }). It is absent on every other asset, so every existing asset reads exactly as before. New refusals: 402 INSUFFICIENT_CREDITS, 403 IMAGE_UPSCALE_NOT_ENTITLED, 409 IMAGE_UPSCALE_IN_PROGRESS and IMAGE_UPSCALE_UNPRICED, 422 IMAGE_UPSCALE_TARGET_NOT_LARGER and IMAGE_UPSCALE_SOURCE_UNREADABLE. See Upscaling images.

2026-10-04 — Image size and reference images on runs (API 1.8.0)​

Additive. POST /runs and POST /runs/estimate accept two optional fields for image runs:

  • imageSize (1K, 2K or 4K; default 1K) sets the size of each image. A larger size is priced higher; the price is quoted by POST /runs/estimate and pinned onto the run at create time.
  • referenceSource (PRIMARY or ALL; default PRIMARY) chooses whether each product's primary image alone, or the primary image followed by its gallery (at most 14 images), seeds the creative.

New responses: 403 IMAGE_SIZE_NOT_ENTITLED when the plan does not include the requested size, and 409 IMAGE_SIZE_UNPRICED when the size has no published price yet. Both are decided before anything is written or charged. Either field with modality: "video" answers 400. A request that sends neither field, or only the defaults, behaves exactly as before and keeps its Idempotency-Key replays. See Runs.

2026-09-30 — Create, edit, delete and restore campaigns; campaigns:write (API 1.7.0)​

Additive. Four new operations, needing a key with the new campaigns:write scope (which implies read, and must expire within 365 days) and a plan that includes campaigns:

  • POST /campaigns creates a campaign for a brand and opens one empty creative (NOT_GENERATED) per product. It spends no credits. Idempotency-Key is required; the answer is 201 with a Location header and an ETag.
  • PATCH /campaigns/{id} renames a campaign and merges your own metadata.
  • DELETE /campaigns/{id} deletes a campaign and every creative in it, and POST /campaigns/{campaignId}/undelete restores them. Nothing is destroyed, and there is no time limit.

New responses: 403 LIMIT_EXCEEDED when a create, or a restore, would pass your plan's campaign limit (a plan limit is a 403, never a 429); 409 CAMPAIGN_NOT_DELETABLE when the campaign holds creatives that are QUEUED or GENERATING; 409 CAMPAIGN_CASCADE_PENDING (with Retry-After) while a previous delete or restore is still finishing its creatives. GET /campaigns and GET /campaigns/{id} accept ?showDeleted=true, and the Campaign object gains the optional metadata and deletedAt. The ETag of a campaign covers its whole representation, including status and creativeCount, so If-Match answers 412 PRECONDITION_FAILED on any change to it. The problem body gains the optional generatingCount and reason (on CAMPAIGN_NOT_DELETABLE) and limitKey, limit and current (on LIMIT_EXCEEDED). The four operations answer 503 WRITES_PAUSED while writes are paused for your account and share one limit of 60 requests per key per minute. See Managing campaigns.

2026-09-30 — Write events over webhooks​

Additive. Changes to creatives, assets and campaigns now send webhooks to your endpoint, whether they were made through the API, in the dashboard or through a review link: creative.approved, creative.rejected, creative.updated, creative.deleted, creative.restored, asset.updated, asset.archived, asset.unarchived, asset.deleted, asset.restored, campaign.created, campaign.updated, campaign.deleted and campaign.restored. Each payload is { type, timestamp, data, previousAttributes?, actor }; data is the resource as it is when the notification is sent. Delivery needs a plan with the webhooks feature. Events made before your endpoint exists are not replayed, and delivery order isn't guaranteed — order by timestamp. run.completed is unchanged apart from needing the same plan feature. See Webhooks.

2026-09-30 — Edit, archive, delete and restore assets; assets:write (API 1.6.0)​

Additive. Five new operations, needing a key with the new assets:write scope (which implies read and must expire within 365 days):

  • PATCH /assets/{id} (updateAsset) edits favorited, notes and your own metadata.
  • POST /assets/{assetId}/archive (archiveAsset), POST /assets/{assetId}/unarchive (unarchiveAsset) and POST /assets/{assetId}/undelete (undeleteAsset) change an asset's state. They require an Idempotency-Key.
  • DELETE /assets/{id} (deleteAsset) deletes an asset. Nothing is destroyed: it is restorable with no time limit.

All five accept If-Match, answer 412 PRECONDITION_FAILED for a stale one, and answer 503 WRITES_PAUSED while writes are paused. They share a budget of 60 requests per key per minute. GET /assets and GET /assets/{id} accept ?showDeleted=true, the Asset object gains the optional metadata, notes and deletedAt, and GET /assets/{id} now returns a strong ETag. See Managing assets.

@mocart-io/api 1.6.0 adds the five operations.

2026-09-30 — Edit a creative; metadata on creatives (API 1.5.0)​

Additive. PATCH /creatives/{id} (updateCreative) sets a creative's delivery aspectRatio and merges your own metadata map (a string sets a key, null removes it; at most 50 keys, names up to 40 characters, values up to 500). It answers with the creative and a fresh ETag, accepts If-Match and an optional Idempotency-Key, and is limited to 60 requests per key per minute, shared with delete and restore. The Creative object gains the optional metadata, present only when it holds a key. See Managing creatives.

2026-09-30 — Delete and restore creatives; showDeleted; creative ETag (API 1.4.0)​

Additive. DELETE /creatives/{id} (deleteCreative) deletes a creative and returns it with deletedAt set; POST /creatives/{creativeId}/undelete (undeleteCreative) restores it. Both need a key with creatives:write, accept If-Match, and answer 503 WRITES_PAUSED while writes are paused. A creative that is QUEUED or GENERATING answers 409 CREATIVE_NOT_DELETABLE with its status in currentStatus; a creative deleted together with its campaign answers 409 CREATIVE_CAMPAIGN_DELETED until the campaign is restored. GET /creatives and GET /creatives/{id} accept ?showDeleted=true, which includes deleted creatives, each carrying deletedAt. GET /creatives/{id} now returns a strong ETag: the value If-Match is compared against, and the same one approveCreative and rejectCreative return.

2026-09-30 — Approve and reject now need creatives:review; write keys expire (API 1.3.0)​

Declared scope change. POST /creatives/{creativeId}/approve (approveCreative) and POST /creatives/{creativeId}/reject (rejectCreative) now need a key with the new creatives:review scope, which implies read. creatives:write no longer approves or rejects: it now means editing, deleting and restoring creatives. No live key held creatives:write when this changed. If you review creatives through the API, mint a key with creatives:review. See Reviewing creatives and Authentication.

Write keys expire. A new key with runs:write, creatives:review, creatives:write or assets:write must expire within 365 days, and the reminder email 14 days before expiry covers all of them. A runs:write key created before this change keeps working without an expiry.

2026-09-30 — RateLimit headers report the limit you will hit first​

Fixed. Same headers, same format — only the values change. RateLimit and RateLimit-Policy used to report whichever limit was checked last, so a read could show limit=1000 (the account-wide cap) while the per-key limit of 200 was the one about to reject you. They now report the limit you are closest to exhausting — normally your key's own — and RateLimit-Policy lists every limit that applied to the request, closest first, for example RateLimit-Policy: 200;w=60, 1000;w=60, 2000;w=60. No status code, body or header name changes, and Retry-After on a 429 is unchanged. Generation and creative-review responses now also list their account-wide and per-IP caps in RateLimit-Policy. See Rate limits.

2026-09-29 — Paused writes answer 503 WRITES_PAUSED (API 1.2.0)​

Additive. POST /creatives/{creativeId}/approve (approveCreative) and POST /creatives/{creativeId}/reject (rejectCreative) can now answer 503 with code WRITES_PAUSED and a Retry-After header. Mocart returns it while API writes are paused — for every account, or for yours alone. Nothing changes and the Idempotency-Key isn't used up, so wait Retry-After and resend the same request with the same key. Reads, including GET /creatives and GET /creatives/{id}, are never paused. See Reviewing creatives.

@mocart-io/api 1.2.0 includes the new response in its error types.

2026-09-29 — Approve and reject creatives through the API (API 1.1.0)​

Additive. The API's first writes outside Generation — nothing about any existing operation changes:

  • POST /creatives/{creativeId}/approve (approveCreative) and POST /creatives/{creativeId}/reject (rejectCreative, with an optional rejectionReason, 1–500 characters). Both return 200 with the same creative body GET /creatives/{id} returns, plus an ETag.
  • A new scope, creatives:write, which implies read. A key with it must expire within 365 days, and the account owner gets an email 14 days before it does.
  • Idempotency-Key is required on both, with the same codes POST /runs uses (IDEMPOTENCY_KEY_REQUIRED, INVALID_IDEMPOTENCY_KEY, IDEMPOTENCY_KEY_REUSED, IDEMPOTENCY_IN_PROGRESS).
  • Repeating a review is safe: approving an approved creative, or rejecting a rejected one, is a 200 that changes nothing. The opposite review is 409 CREATIVE_NOT_PENDING, and the problem body carries a new currentStatus member.
  • A rate budget of their own: 60 requests/min per key, 300 per account and 600 per IP by default, separate from every read budget.

@mocart-io/api 1.1.0 adds approveCreative and rejectCreative. See Reviewing creatives.

2026-09-28 — docs.mocart.io launches; Generation payload cleanup​

Additive. This site launches as the home for Mocart API documentation: guides for getting started, the full request/response lifecycle, and a generated API reference kept in sync with the API's own OpenAPI document.

Breaking, shipped with this launch and covered by a zero-traffic waiver (the Generation plane had no external callers before this release, so the usual 90-day notice was waived rather than delayed):

  • Timestamps are now ISO 8601 UTC strings, not epoch milliseconds — on the Run object's createdAt and finalizedAt, and on estimatedCompletion.completesAt.
  • Every Generation-plane object carries an object type tag ("run", "run_item", "preset") next to its typed identifier (runId, jobId, presetId). This applies to the Generation plane only — Catalog, Campaigns & Creatives, and Deliveries objects are unchanged and carry no object field. A preset's identifier is now presetId (was id), matching the presetId you send when creating a run.
  • Credits have one shape everywhere: credits: { estimated, rateCardVersion }, replacing the create response's separate estimatedCredits / rateCardVersion fields.
  • One Run object shape for create, get, and list — the same fields, including counts, come back from createRun, getRun, and each row of listRuns (a non-final list row omits counts, since a live tally isn't computed for list rows).
  • GET /presets (listPresets) replaces the old /runs/presets, and now returns the full list envelope ({ object: "list", data, pagination }) instead of a bare { data }.
  • GET /runs/products is removed. Use GET /products?brandId= (listProducts) instead — it serves the same catalog, with the same filtering, under the resource it actually belongs to.

If you're integrating today, build against the shapes shown in these guides — they document the API as of this launch, not any earlier, unpublished behavior.