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-Keyis required; the answer is202with the upscale and aLocationheader. Credits are held when it is accepted and charged only for a delivered image.GET /upscales/{upscaleId}(getImageUpscale) reads it:statusisQUEUED,PROCESSING,SUCCEEDEDorFAILED.
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,2Kor4K; default1K) sets the size of each image. A larger size is priced higher; the price is quoted byPOST /runs/estimateand pinned onto the run at create time.referenceSource(PRIMARYorALL; defaultPRIMARY) 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 /campaignscreates a campaign for a brand and opens one empty creative (NOT_GENERATED) per product. It spends no credits.Idempotency-Keyis required; the answer is201with aLocationheader and anETag.PATCH /campaigns/{id}renames a campaign and merges your ownmetadata.DELETE /campaigns/{id}deletes a campaign and every creative in it, andPOST /campaigns/{campaignId}/undeleterestores 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) editsfavorited,notesand your ownmetadata.POST /assets/{assetId}/archive(archiveAsset),POST /assets/{assetId}/unarchive(unarchiveAsset) andPOST /assets/{assetId}/undelete(undeleteAsset) change an asset's state. They require anIdempotency-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) andPOST /creatives/{creativeId}/reject(rejectCreative, with an optionalrejectionReason, 1–500 characters). Both return200with the same creative bodyGET /creatives/{id}returns, plus anETag.- A new scope,
creatives:write, which impliesread. A key with it must expire within 365 days, and the account owner gets an email 14 days before it does. Idempotency-Keyis required on both, with the same codesPOST /runsuses (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
200that changes nothing. The opposite review is409 CREATIVE_NOT_PENDING, and the problem body carries a newcurrentStatusmember. - 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
Runobject'screatedAtandfinalizedAt, and onestimatedCompletion.completesAt. - Every Generation-plane object carries an
objecttype 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 noobjectfield. A preset's identifier is nowpresetId(wasid), matching thepresetIdyou send when creating a run. - Credits have one shape everywhere:
credits: { estimated, rateCardVersion }, replacing the create response's separateestimatedCredits/rateCardVersionfields. - One
Runobject shape for create, get, and list — the same fields, includingcounts, come back fromcreateRun,getRun, and each row oflistRuns(a non-final list row omitscounts, 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/productsis removed. UseGET /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.