Design Import
Alpha
The contract may change without notice, and imports created with it are not guaranteed to remain compatible. Not covered by the deprecation policy.
The Design Import API creates a new Abyssale design from a single JSON payload. It is tool-agnostic and designed to be easy to produce by API consumers and AI agents.
Where the design lands
An import creates a design inside one of your projects, so it needs to be told which — target.project_uuid is required:
"target": { "kind": "design", "project_uuid": "3f5a7b16-…" }To create a reusable workspace template instead — a blueprint you duplicate into a project rather than generate from directly — pass "target": { "kind": "workspace_template" }, which takes no project. Those are not addressable through the Designs API, so as-import can only round-trip designs.
Field reference and error codes: Create Import → Target. Which one you want: Designs vs Workspace Templates.
The request body uses an element-centric schema that mirrors the response of GET /designs/{uuid}: layers live at the top level with per-format layout and properties, instead of being nested inside each format.
Start from a real payload instead of a blank page
The fastest way to learn this format is to read one. Build a design in the Abyssale editor, then call GET /designs/{uuid}/as-import — it returns that design as a complete, valid import body you can edit and re-POST. Worth doing before hand-authoring anything.
Pick a design type
Four design types can be imported. Pick the one that matches what you're building — each card links to its rules.
animation timeline plus video / audio layers on top of the static model.pixelshex colors+ timelineprinterSingle-page print PDFPrint-ready artwork authored at real-world size, with bleed and safe zones.mm / inpt fontsCMYKprinter_multipageMulti-page print PDFOne print setup plus an ordered pages[] array. Brochures, catalogues, booklets.mm / inpt fontsCMYKLimitations
- The endpoints accept company API keys only. User API keys receive
401 Unauthorized. - Brand auto-fill is not available — every logo layer must declare an explicit
src(filename, public URL, or data URI). - Design settings and per-layer generation settings are not available yet (Alpha) — an import carries the design's content (formats, layers, properties) only. Configure those in the platform after the import.
Process overview
Importing a template is an asynchronous, multi-phase flow. Follow the steps in order — each one has its own page in this section.
POST /designs/import/jsonPOST the full design structure (formats + layers) as JSON. The API validates the schema, stores the structure, and returns the import id, a links block, and a flat uploads[] array — one entry per asset you must upload.
For every entry in uploads[], POST the binary directly to its presigned URL. If uploads[] is empty, skip this step entirely.
PUT confirms every expected asset is in place and queues the import for backend processing. The links.dashboard URL arrives from the status poll, once the import is DONE.
GET /designs/import/json/{id}GET the current status at any time. The response carries next_check_after_ms so you never have to guess the polling cadence — repeat until DONE.
Do you need steps 2 & 3?
It depends entirely on how your assets are referenced. The POST response tells you which path you're on via the uploads[] array.
All src are public URLs or data URIs
uploads[] comes back empty and the import is already QUEUED. Skip steps 2 & 3 — go straight to polling (step 4) until DONE.
Some src are local filenames
uploads[] lists one entry per file. Upload each one (step 2), then PUT to validate (step 3) before polling.
Dry-run validation
Pass ?validate_only=true to POST to check a payload without creating anything — 204 No Content on success. Three checks needing a round-trip are skipped; see validate_only.
Status lifecycle
Read it left-to-right as action → resulting status: each label is the call you make (or the backend step), and the pill right after it is the status you'll then see when you poll.
Exactly which status a call sets:
| After you call… | Status becomes | Set by |
|---|---|---|
POST — some src are local filenames (assets still to upload) | WAITING_FOR_VALIDATION | API |
POST — all src are public URLs / data URIs (sync short-circuit) | QUEUED | API |
PUT — validate (only after uploading the pending assets) | QUEUED | API |
| backend picks the import off the queue | PROCESSING | Backend |
| backend finishes building + rendering previews | DONE (or FAILED) | Backend |
Polling GET /designs/import/json/{id} is the only way to know when the imported design is ready — preview thumbnails are rendered for you, so the payload never carries a preview image.
Drive off terminal states, not off PROCESSING
A fast import can go from QUEUED straight to DONE between two polls, so you may never observe PROCESSING. Write your polling loop against the terminal states (DONE / FAILED). It always terminates: an import you never validate is reported FAILED once its one-hour upload window lapses — see PROCESSING is a projection and Abandoned imports terminate.
See Import Status for the full status reference, warnings surface, and error format.
Error contract
Design Import uses the same error envelope as every other endpoint — a top-level id and message, with field-level detail in errors[]:
{
"id": "out_of_range",
"message": "font_size must be between 2 and 1000",
"errors": [
{
"path": "layers[2].properties.font_size",
"code": "out_of_range",
"message": "font_size must be between 2 and 1000",
"expected": { "min": 2, "max": 1000 },
"received": 1500
}
]
}Array indices in path are always bracketed (formats[0].width, pages[1].layers[0].properties.color) — there is no dotted-index form.
The one body that is not this envelope is a failed presigned upload: that is raw S3 XML, because the request never reaches Abyssale. The full list of error codes lives on Import Status.
End-to-end cURL walkthrough
Replace $API_KEY with your company API key. Full request/response details for each call live on the individual phase pages.
1. (Optional) Dry-run the payload.
curl -X POST 'https://api.abyssale.com/designs/import/json?validate_only=true' \
-H "x-api-key: $API_KEY" \
-H "Content-Type: application/json" \
-d @template.json2. Submit the structure.
curl -X POST https://api.abyssale.com/designs/import/json \
-H "x-api-key: $API_KEY" \
-H "Content-Type: application/json" \
-d @template.jsonThe response contains the import id, a links block, and one uploads[] entry per asset.
3. Upload each asset to its presigned URL.
For every entry in uploads[], replay each fields entry as a form part, then append the binary:
curl -X POST "$UPLOAD_URL" \
-F "key=$FIELDS_KEY" \
-F "Content-Type=image/jpeg" \
-F "policy=$FIELDS_POLICY" \
-F "x-amz-signature=$FIELDS_SIGNATURE" \
-F "file=@./hero.jpg"4. Validate the import.
Skip this step if uploads[] was empty in the POST response.
curl -X PUT "$VALIDATE_URL" \
-H "x-api-key: $API_KEY"$VALIDATE_URL is links.validate from the POST response.
5. Poll status until processing completes.
curl "$STATUS_URL" \
-H "x-api-key: $API_KEY"$STATUS_URL is links.status from the POST or PUT response.
Related
- Designs — the schema this endpoint mirrors
GET /designs/{uuid}/as-import— round-trip an existing design back into an import payload- Workspace Templates — duplicate an imported template into projects
- Authentication — required for all API requests
