API Changelog
The API is versioned by release date — vYYYY-MM-DD, never a counter. Every JSON object response, success and error alike, carries a top-level version field naming the version that answered it, so you can always tell which contract produced a response.
v2026-08-17
Breaking Action required
- One error envelope on every endpoint and every status —
{id, message, errors?}.errorsis now a flat array, and the code moved to top-levelid. Migration steps. - Framework failures answer real codes —
method_not_allowed,invalid_json,internal_error— instead of the placeholdererror. - Three misspelled 404 codes are corrected, each of which held a literal space:
project_not found→project_not_found, and likewise forworkspace_template_not_foundandduplication_request_not_found. - Every JSON object response carries a top-level
version. Clients that reject unknown fields must accept it. GET /designsandGET /projectshide deleted records — a design whose project was deleted is no longer listed, and onlyCREATEDprojects are returned.messageno longer begins with the HTTP status number —"404 Design … not found"is now"Design … not found". A client that slices or matches that prefix breaks silently. Branch onidinstead; see Errors.- An unknown format answers
404, not400, on synchronous generation — matching every other endpoint and the reference. - Per-workspace rate limiting, with a new
429code. Endpoints now carry a per-minute and per-hour budget by route class and answer429 request_rate_limitedwhen you exceed it. This is a differentidfromrate_limit_exceeded, which keeps its meaning — out of credits, or the global ceiling. A client that branches onrate_limit_exceededalone will not recognise a throttle. Retryrequest_rate_limited.rate_limit_exceededcovers two situations andmessagetells them apart — back off when it is the global ceiling, top up or upgrade when it is credits or plan, which no amount of retrying fixes.feature_not_in_planis never worth retrying. Every response now carriesX-RateLimit-Limit,X-RateLimit-RemainingandX-RateLimit-Reset, and a refusal addsRetry-After— pace against those rather than retrying blind. The budget is per workspace, so extra API keys do not raise it.
Added
- Design import Alpha —
POST /designs/import/jsonbuilds a design or workspace template from JSON (static,animated,printer,printer_multipage), withGET/PUT /designs/import/json/{importId}to poll and validate. The design type is checked against your plan before anything is created —animatedneeds MP4,printerandprinter_multipageneed Printable PDF — and a workspace without it gets a new429 feature_not_in_plan, on?validate_only=trueas well. GET /designs/{designId}/as-importAlpha — export any design in the exact shape the import accepts.GET /workspace-templatesandGET /workspace-template-categorieslist the workspace's shared templates and their categories.- Designs report their project —
project_id/project_nameon the listing and detail endpoints, plus aproject_idfilter.category_id/category_nameremain as deprecated aliases. GET /designs/{designId}returns the full design model — print geometry (dpi,bleed_size,safe_size), the animatedanimationblock with per-element timing and tweens, andpages[]pluselements_per_page(keyedpage_1…page_N) for multi-page print designs.?i=advancedreturns group layers, withlayer_ids, per-formatlayout, and auto-layout settings. Without the parameter the response keeps its previous shape.- Webhook payloads carry
event_type— route on it instead of inferring the event from the payload's shape.
Fixed
- A deleted design answers
404, not400, on the generation endpoints, with the sametemplate_not_foundcode a design that never existed already returned. - No error response is empty.
GET /banners/{bannerId}, the banner listing, ZIP export with unknown ids, a failed workspace-template duplication and the public renderer all returned a JSON content type with no body. POST /public/asset/importanswers400, not500, whenasset_nameis missing.- Field-level detail always uses
{path, code, message}— errors relayed from the generation engine used to arrive as{field, message}, and now carry anidrather than prose alone. - Plan and credit gates on asynchronous generation answer
429 rate_limit_exceededinstead of a400whose message merely started with "429".
Deprecated Plan a migration
remove_bgon synchronous generation — still works, but it can push the call past the endpoint's 10-second cap. Remove backgrounds asynchronously.auto_focusis unaffected.text_to_imageis gone from the synchronous schema. It was always rejected there.- The
category_idquery parameter onGET /designs— filter withproject_id. Existing requests keep working. auto_focus_properties.model: "face"— use"people"withfocus_framing: "face". It detects faces with the same model and also honoursfocus_zoomandfocus_target, which"face"ignores.focus_framingalready defaults to"face", so switching to"model": "people"is usually the whole migration."face"keeps working.
Changed
GET /fontssorts case-insensitively.
Before v2026-08-17
Releases before the versioning scheme carried no version name and no version payload field.
