Abyssale API Reference — Image, Video & PDF Generation API (v2026-08-17)

Download OpenAPI specification:

License: Proprietary

Abyssale API Reference

Abyssale is a programmatic creative asset generation API for creating marketing visuals, ad creatives, videos, and print-ready PDFs at scale. Design once in Abyssale, then generate thousands of personalized variations — across every format, channel, and language — via a single REST API call.

This documentation is about the Abyssale Rest API. If you would like to discover other Abyssale features or to have a comprehensive understanding on how everything works, go to the Abyssale developers hub.

Base URL: https://api.abyssale.com

API versioning

The API is versioned by release date: vYYYY-MM-DD (e.g. v2026-08-17), never a counter. Every JSON object payload the API returns — success and error alike — carries a top-level version field naming the version that answered. The changelog (CHANGELOG.md) lists every version and what changed in it, breaking changes first.

General Rest API information:

  • Data exchange format: JSON. All API calls must contain the Content-Type: application/json header.
  • Rate limits: 10 requests/second
  • Usage counts successful visual creation only — a call that generates nothing (a read, a failed generation, a dry-run validation) does not consume it. Visuals created from the API console on a design page count too.
  • Trial plan: 30 API calls included.
  • Available Integrations: Zapier, Make (ex Integromat), Airtable & n8n (if you prefer using nocode)

Which approach should I use?

Use case Recommended approach
Personalized images in emails (rendered on open) Static design → Dynamic image URL
Embed a live, customizable image on a website Static design → Dynamic image URL
Single image on demand (synchronous response) Synchronous generation
Bulk campaign creatives across multiple formats Async generation
Animated GIF or video for social/ads Animated design → Async generation with gif or video options
HTML5 banner ads for ad networks Animated design → Async generation with html5 options
Print-ready PDF (single page) Printer design → Async generation with print options
Multi-page print document (brochure, catalog) Printer multipage design → Multi-page PDF

Authentication

Abyssale uses API Key Authentication. Your API key is passed along with every API call, allowing Abyssale to identify your company account and access your data.

How to get your API Key?

api_key

All of your Abyssale requests must contain a x-api-key header with your API Key.

Security Scheme Type: API Key
Header parameter name: x-api-key

Common error responses (applicable to all endpoints):

  • 401 Unauthorized — missing or invalid API key
  • 429 Too Many Requests — a rate limit was exceeded, or credits are insufficient
  • 500 Internal Server Error — unexpected server-side failure

Rate limits

Two limits apply to every request, one after the other. Both answer 429, and the id in the body tells you which one you hit.

# Limit Applies to Budget id
1 Global ceiling Every endpoint 10 requests/second rate_limit_exceeded
2 Endpoint budget Endpoints in a limited tier see below request_rate_limited

1. The global ceiling is 10 requests per second across every endpoint, checked first. It is a burst guard: sustain that rate and you will exhaust most endpoint budgets within the minute anyway, so in practice it is the endpoint budget you meet first.

2. Endpoint budgets. Every endpoint belongs to ONE tier, and the tier sets the budget. The table below covers every endpoint in this reference; a handful of internal and deprecated routes are limited too, but they are not part of the published contract. Each tier is counted separately, so writes never eat into your read budget, and each budget is enforced over two windows at once — per minute and per hour — both of which must pass. Generation endpoints are in no tier: nothing below bounds them per minute or per hour, only the global ceiling and your plan's credits.

Endpoint Tier Per minute Per hour
GET /designs Read 120 3 000
GET /designs/{designId} Read 120 3 000
GET /designs/{designId}/formats/{formatSpecifier} Read 120 3 000
POST /designs/{designId}/dynamic-image-url Write 60 1 200
GET /designs/{designId}/as-import Write 60 1 200
GET /designs/import/json Read 120 3 000
POST /designs/import/json Write 60 1 200
POST /designs/import/json?validate_only=true Validation 120 2 400
GET /designs/import/json/{importId} Polling 600 18 000
PUT /designs/import/json/{importId} Write 60 1 200
GET /design-duplication-requests/{duplicateRequestId} Polling 600 18 000
GET /banners/{bannerId} Read 120 3 000
POST /async/banners/export Write 60 1 200
GET /fonts Read 120 3 000
GET /projects Read 120 3 000
POST /projects Write 60 1 200
GET /workspace-templates Read 120 3 000
GET /workspace-template-categories Read 120 3 000
POST /workspace-templates/{companyTemplateId}/use Write 60 1 200
GET /generation-request/{generationRequestId} Polling 600 18 000
POST /auth Read 120 3 000
POST /banner-builder/{designId}/generate none
POST /async/banner-builder/{designId}/generate none
POST /async/banner-builder/{designId}/generate-multipage-pdf none

Write is the tightest because one call is several database writes, two or more storage uploads, a queue publish and one outbound fetch per distinct layer src. Count round trips, not calls: exporting a design and importing it back spends two write calls, so 1 200/hour is 600 export–edit–import cycles.

Validation is looser than write so that iterating on a payload until it validates never costs you an import. It is not free — it still decodes every data URI you send.

Polling is the loosest because those endpoints are meant to be called in a loop. An import's status response tells you when to come back (next_check_after_ms, 2 seconds while processing), which is 30 requests a minute for one job. The budget sits far above any sane cadence — honour next_check_after_ms and you will not meet it.

Generation has no endpoint budget — which is not the same as unlimited. The global 10 requests/second ceiling still applies, and your plan's credits gate it on top (id: rate_limit_exceeded for both; on a credit refusal retrying never helps, the plan or the balance has to change). Polling the resulting job IS budgeted, in the polling tier.

Every response from a limited route carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset for whichever window you are closest to exhausting, and a 429 adds Retry-After. Two exceptions, both because the limiter never ran: a 401 (authentication fails first, so there is no workspace to charge) and the rare case where the limiter's backing store is unreachable and it lets the request through. Read those headers and pace your calls — this is a limit you can see coming, which matters most for automated clients and AI agents retrying a rejected payload. One thing worth knowing before you build a retry loop: a refused request still counts, so retrying into a 429 spends your hourly budget.

Deprecation

Some fields in this reference are marked deprecated. That mark is a commitment and a warning, in that order:

  • It keeps working. A deprecated field is still populated and still behaves as documented. Nothing is removed from under a running integration.
  • It will be removed eventually. There is no date, and we do not commit to one. "Not yet" is the only guarantee, so treat a deprecation as work to schedule rather than work to ignore.
  • Each one names its replacement. Read the field's description: it says what to use instead, and whether the two are equal today (template_id duplicates id; category_id / category_name duplicate the project fields on a design).

Deprecation is announced in three places, and only these count: the deprecated flag in this reference, the field's own description, and a dated entry in the changelog.

A route that disappears from this reference is deprecated too. Omission is how a route is retired here: it keeps answering for existing callers, but it is no longer part of the published contract and should not be adopted. If you are calling something you cannot find documented, treat that as a signal, not as an oversight.

Two things this does not cover. Anything marked Alpha — today, the whole Design Import section — may change or vanish without notice and without a changelog entry; that is what the label means. And a new field appearing in a response is not a breaking change, so parse permissively and ignore what you do not recognise.

Webhooks

Asynchronous work — batch generation, exports — finishes on our side, not in your request. You learn about it in one of two ways: poll the job's status endpoint, or receive a webhook. The payloads are documented at the bottom of this reference; what follows is the delivery contract they all share.

Property Detail
Method POST over HTTPS
Content type application/json
Acknowledgement Return 200 or 201 within 15 seconds
Retries The first attempt, then up to 6 retries — about 3 hours in total
Backoff 15 s, 15 s, 3 min, 10 min, 30 min, 2 h
Identifying header X-Referer: api.abyssale.com
Static IPs Available for allow-listing — contact support

Anything other than 200/201 is treated as a failure and retried; 15 seconds is a hard timeout, so acknowledge first and do your work afterwards. Answer 410 Gone to stop the retries permanently — that is how a receiver says the endpoint is retired, and it is the one response that is never retried.

Every payload carries a top-level event_type (NEW_BANNER, NEW_BANNER_BATCH, NEW_EXPORT, …). Dispatch on that field rather than inferring the event from the payload's shape, so a new event type never lands in the wrong branch of your handler.

Two ways to subscribe, and they are not interchangeable. A callback_url in a request body is per-job: it fires once, for that job only. A webhook configured in the Abyssale dashboard is per-workspace: it fires for every matching event, including work started outside the API. NEW_BANNER has no callback_url of its own — it comes from a dashboard webhook.

Design your receiver to be idempotent and to tolerate re-delivery. A retry sends the same payload again, and a webhook is not ordered with respect to your own polling: you may see the job finish through the status endpoint first. Treat the payload as a notification that something is ready, keyed on its id, not as a one-shot event you must not miss.

Requests are not signed. Anyone who learns your callback_url can post to it, so do not treat the payload alone as proof. Use an unguessable callback path, verify against the API (GET /generation-request/{id}, GET /banners/{id}) before acting on anything that matters, and treat X-Referer as a hint rather than authentication.

Designs

A design is a layout including at least:

  • one format (dimension, i.e. Facebook feed 1200x628 or IAB 300x250)
  • one element (text, button, image, shape...)

All elements have their own properties that can be customized (color, text payload, image...).

Once designed, all properties of the design will act as a reference for subsequent image generation. Obviously, those properties can be overridden by sending new parameters in the generation API call.

Abyssale provides a list of free template designs in the Template Library page. Feel free to use one to start designing your design instead of doing it from scratch.

All of your designs are listed in the different project pages, only those are accessible via API.

Design status changed Webhook

A design moved through the approval workflow — submitted for review, approved, rejected, put on hold, or reset. Use it to tell an internal tool that a creative is cleared for use.

Configured in the dashboard, and filterable. Like newBanner, this event has no callback_url on any endpoint: you subscribe once for the workspace. The subscription can be narrowed to specific designs, in which case changes to any other design are not delivered.

The payload is the design plus the status that triggered the event. That status is frozen at the moment of the change, not read at delivery time — if a design is approved and then rejected, you receive two events carrying APPROVED and REJECTED respectively, rather than two copies of the latest value.

Delivery, retries and the event_type convention are described under Webhooks.

Request Body schema: application/json
event_type
required
string
Value: "TEMPLATE_STATUS"

Identifies the event — every webhook payload carries this field.

id
required
string <uuid>

Unique identifier (UUID) of the design whose status changed.

name
string

Name of the design.

created_at
integer

Unix timestamp of the design's creation.

updated_at
integer

Unix timestamp of the last change to the design's content.

status
required
string
Enum: "NONE" "IN_REVIEW" "ON_HOLD" "REJECTED" "APPROVED"

The workflow status this event reports. NONE is the default for a design that has never entered the workflow, and is also what a reset reports.

status_updated_at
required
integer

Unix timestamp at which that status was set.

Responses

Request samples

Content type
application/json
{
  • "event_type": "TEMPLATE_STATUS",
  • "id": "873608a1-e498-47dd-a36d-bd065e3e2b8e",
  • "name": "Ad campaign fall 2025",
  • "created_at": 1623229458,
  • "updated_at": 1649837900,
  • "status": "APPROVED",
  • "status_updated_at": 1649837939
}

List all designs

Retrieve all designs available in your Abyssale workspace. Each design defines the visual layout, available formats (dimensions), and configurable elements (text, images, shapes, etc.) that can be overridden during image generation. Filter by design type or project using query parameters.

A design lives in a project. Organisation-level master designs are workspace templates, live in a category, and are listed by GET /workspace-templates instead — they never appear here.

Authorizations:
api_key
query Parameters
project_id
string <uuid>

Unique identifier (UUID) of a project. Filter designs by project.

type
string
Enum: "static" "animated" "printer" "printer_multipage"

Filter designs by one of these types: static, animated, printer, printer_multipage. An unknown value is ignored and the full unfiltered list is returned — this never answers 400 (long-standing behavior existing integrations rely on).

Responses

Request samples

curl -H "x-api-key: $ABYSSALE_API_KEY" \
  "https://api.abyssale.com/designs?type=static"

Response samples

Content type
application/json
[]

Get design details

Retrieve the full specification of a design: its formats (dimensions and preview URLs), all configurable elements with their properties, and any template variables defined in text layers. Use this endpoint to discover what data to send in a generation request.

For printer designs, each format additionally carries its print settings read-only: dpi (integer render DPI computed at import time, capped at 300), and bleed_size / safe_size (floats in the design's unit). All three are always present on a printer format — a design with no stored print settings, or with a zone disabled, reports the values the renderer itself uses: dpi: 300 and bleed_size / safe_size of 0, where 0 means the zone is off. Read them unconditionally; there is no need to test for the keys.

For animated designs, the response additionally carries the animation model: a top-level animation object (duration — timeline length in seconds; screenshot_at_s — the HTML5 backup-screenshot moment, null on designs created before it was recorded), a per-element animation object (start_at_s, end_at_s, tweens [{id, type, keyframes}] with raw keyframe objects) on elements that carry timing or tweens, and video/audio media attributes (video_url/audio_url, video_duration, video_muted/audio_muted, max_volume) in the standard attribute shape.

Multipage print designs (printer_multipage) return a different shape. A multipage design has no formats — each page is one format row — so the response carries pages and elements_per_page instead of formats, elements and variables. A client coded against formats[] will not work on a multipage design, and because a dynamic image is minted per format, a multipage design has no dynamic_image_url anywhere in the response (it is a static-only feature in any case).

pages[] items are DesignPage objects, not formats: id (page_1 … page_N), width, height, unit and preview_url — no format uid. elements_per_page is an object keyed by page id (not an array), each value being that page's element list.

The print settings are returned once, at the rootdpi, bleed_size and safe_size describe the document, not a page, because a multipage design has a single print setup. That is the same reason the import declares them at the top level. A page does not repeat them.

Group layers require ?i=advanced. By default this endpoint returns its original released shape, in which no group layer appears at all — its children are listed individually and the group itself is invisible. Pass i=advanced to have them injected (into elements, or into each page's list in elements_per_page on a multipage design). Each one carries layer_ids (one list for the whole design — a group holds the same children in every format), a per-format layout, its computed hidden / locked, and, except on animated designs, a per-format group block of auto-layout settings (auto_layout / direction / placement / gap).

A masked group — one whose members are clipped to a shape — is still type: "group" and additionally carries mask, keyed by format. The geometry can differ per format, but a group is masked in all of its formats or in none, so the presence of mask is what distinguishes a masked group from a plain one.

The per-format read GET /designs/{designId}/formats/{formatSpecifier} is always the advanced view and needs no parameter; there layout, mask and group are flattened to the single format.

Authorizations:
api_key
path Parameters
designId
required
string <uuid>

Unique identifier (UUID) of the design. A value that is not a UUID does not match the route and answers 404 with id: endpoint_not_found.

query Parameters
i
string
Value: "advanced"

Pass advanced to receive the design's full layer set and property set — notably group layers, which are omitted from the default response. Any other value (or none) returns the default shape.

Responses

Response samples

Content type
application/json
{
  • "id": "64238d01-d402-474b-8c2d-fbc957e9d290",
  • "template_id": "64238d01-d402-474b-8c2d-fbc957e9d290",
  • "name": "Ad campaign fall 2025",
  • "type": "static",
  • "created_at": 1649942114,
  • "updated_at": 1649942114,
  • "project_id": "9d1f2b7c-5a44-4c3e-9f21-0b8e6d4a1c73",
  • "project_name": "Fall campaigns",
  • "category_id": "9d1f2b7c-5a44-4c3e-9f21-0b8e6d4a1c73",
  • "category_name": "Fall campaigns",
  • "version": "string",
  • "formats": [],
  • "elements": [
    ],
  • "variables": {
    },
  • "animation": {
    },
  • "dpi": 300,
  • "bleed_size": 3,
  • "safe_size": 5,
  • "pages": [
    ],
  • "elements_per_page": {
    }
}

Get format details

Retrieve detailed information for a specific format within a design, including dimensions, unit, preview URL, dynamic image URL, element layout, and variables. Useful for inspecting a single format before generating or building dynamic image URLs.

For printer designs the response additionally carries the format's print settings read-only: dpi (integer render DPI computed at import time, capped at 300), and bleed_size / safe_size (floats in the design's unit, 0 when the zone is off). All three are always present on a printer format; a design with no stored print settings reports the renderer's own defaults (dpi: 300, zones 0).

For animated designs the response additionally carries the design's animation object (duration, screenshot_at_s — seconds), per-element animation timing/tween data, and video/audio media attributes — the same read surface as GET /designs/{designId}.

This endpoint is always the advanced view. Unlike GET /designs/{designId}, it needs no i=advanced parameter: it always returns the full property set and always injects the format's group layers, flattened to this single format (layout, the group block, and the computed hidden / locked are plain values, not keyed by format name). A group that does not exist in the requested format is not emitted.

Multipage print designs (printer_multipage): a multipage design is one document — it has no formats, and its pages are not addressable through this endpoint. Any formatSpecifier yields 404 with id: format_not_found. Read the whole document with GET /designs/{designId} or export it with GET /designs/{designId}/as-import.

Authorizations:
api_key
path Parameters
designId
required
string <uuid>

Unique identifier (UUID) of the design. A value that is not a UUID does not match the route and answers 404 with id: endpoint_not_found.

formatSpecifier
required
string

Format name or UID. Prefer the UID — it survives a rename.

Not applicable to printer_multipage designs: they have no formats, so every value here answers 404 with id: format_not_found. See the endpoint description.

Responses

Response samples

Content type
application/json
{
  • "id": "facebook-post",
  • "uid": "9b57d65e-eb2c-4a74-a51e-4482917c248a",
  • "width": 1200,
  • "height": 1200,
  • "unit": "px",
  • "dpi": 300,
  • "bleed_size": 3.5,
  • "safe_size": 5,
  • "animation": {
    },
  • "design": {
    },
  • "elements": [
    ],
  • "variables": {
    },
  • "version": "v2026-08-17"
}

Asset Generation

Generate images, animated videos, GIFs, HTML5 banners, and print-ready PDFs programmatically from your Abyssale designs.

The generation workflow is simple:

  1. Build your design in Abyssale (or pick one from the Template Library)
  2. Send your dynamic data — text, images, colors — via the API
  3. Receive the generated asset URL in the response

Use the synchronous endpoint for single-image generation with an immediate response. Use the asynchronous endpoint for bulk banner creation across multiple formats at once — ideal for marketing automation, personalized ad campaigns, and batch asset production at scale.

Generate an image (synchronous)

Synchronously generate a single image from a design. Send your dynamic data (text, images, colors) as element overrides in the request body and receive the generated file URL immediately in the response.

static designs only. An animated, printer or printer_multipage design answers 400 with id: template_not_static — use asynchronous generation instead. This is a property of the endpoint, not of your plan: a video or a print PDF cannot be produced inside a synchronous request, so there is no combination of parameters that makes this work.

Best for: real-time image generation, single-asset workflows, or when you need the result inline without polling. The rendering budget is hard-capped at 10 seconds: the call returns the finished file within that window or fails with 500 internal_server_error (typical renders take tens of milliseconds).

For bulk generation across multiple formats, use the asynchronous endpoint instead.

AI features and the 10-second cap. text_to_image is not accepted here — an AI generation takes far longer than the budget, so the field exists only on the asynchronous endpoint and sending it answers 400 invalid_payload. Background removal (remove_bg) is accepted but is deprecated on this endpoint: it adds an AI round-trip before the render, so a large or slow source image can push the call past the cap and fail with 500 internal_server_error rather than returning your asset. Existing integrations keep working; new ones should remove backgrounds asynchronously. auto_focus is unaffected and fully supported here — it runs on Abyssale's own detection model and is fast enough that it does not put the budget at risk.

All body fields are optional — omit elements to render the design's saved default content. A template_format_name that does not exist on the design answers 404 with id: format_not_found (the format is addressed as a resource, so this is a 404 by contract, not a 400).

Authorizations:
api_key
path Parameters
designId
required
string <uuid>

Unique identifier (UUID) of the design. A value that is not a UUID does not match the route and answers 404 with id: endpoint_not_found.

Request Body schema: application/json
required
object

Element overrides keyed by layer name. On this synchronous endpoint every value MUST be an object — a bare string value answers 400 invalid_payload (the asynchronous endpoint is lenient).

template_format_name
string

Format ID to generate. Optional — when omitted, the design's first format is generated.

image_file_type
string
Enum: "png" "jpeg" "webp" "avif" "pdf" "auto"

Output file type (auto ≡ omitted). When omitted: jpeg, or png when the format's background color is transparent.

original_visual_id
string <uuid>

Regenerate an existing visual in place, keeping its share URL (visual versioning). Unknown or unrelated visuals answer 404 (visual_not_found, not_related_to_same_template, not_related_to_same_format).

file_compression_level
integer [ 1 .. 100 ]

Output quality, 1-100 — 100 is the best quality (least compression), 1 the smallest file. Despite the name, higher = better.

Responses

Request samples

Content type
application/json
{
  • "template_format_name": "facebook-post",
  • "image_file_type": "png",
  • "elements": {
    }
}

Response samples

Content type
application/json
{}

Generate multiple formats (asynchronous)

Asynchronously generate multiple formats of an image, video, GIF, HTML5 banner, or PDF in a single API call. Ideal for bulk banner production, multi-channel marketing campaigns, and programmatic ad creative generation at scale.

Specify one or more format IDs to generate, or omit the list to generate all formats defined in the design. Batches are queued and worker-driven, with no hard completion bound — most complete within a couple of minutes; rely on the webhook or polling, never a fixed wait. Provide a callback_url to receive a webhook notification when generation is complete, or poll GET /generation-request/{generationRequestId} for status.

You can find available formats and elements by calling GET /designs/{designId}.

This is where AI image work belongs. Having no completion bound, this endpoint is the only one that accepts text_to_image (AI generation and inpainting), and it is the recommended home for remove_bg too — both add an AI round-trip that the synchronous endpoint's 10-second cap cannot absorb.

All body fields are optional — omit elements to render the design's saved default content, and omit template_format_names to generate every format. A template_format_names entry that does not exist on the design answers 404 with id: format_not_found (the format is addressed as a resource, so this is a 404 by contract, not a 400).

Authorizations:
api_key
path Parameters
designId
required
string <uuid>

Unique identifier (UUID) of the design. A value that is not a UUID does not match the route and answers 404 with id: endpoint_not_found.

Request Body schema: application/json
required
object (AsyncElements)

Same as Elements, but its image element also exposes AI generation properties (text_to_image, inpainting, background removal model) that are only available for asynchronous generation.

template_format_names
Array of strings

Format IDs you would like to generate.

The array can be empty or not provided if you want to generate all formats of this design.

callback_url
string <uri>

The url that will be called once generation of your images is done.

We will do a POST request of the NewBannerBatch event on this URL with a JSON payload.

image_file_type
string
Enum: "png" "jpeg" "webp" "avif" "gif" "pdf" "html5" "mp4" "auto"

Output file type (auto ≡ omitted); must match the design type, else 400 invalid_filetype — static → jpeg/png/webp/avif/pdf, animated → mp4/gif/html5, print → pdf. When omitted: static → jpeg (png when the format background is transparent); animated → mp4; print → pdf.

file_compression_level
integer [ 1 .. 100 ]

Output quality, 1-100 — 100 is the best quality (least compression), 1 the smallest file. Despite the name, higher = better.

object

HTML5 output options (animated designs only)

object

GIF output options (animated designs only)

object

Video output options (animated designs only)

object

Print output options (printer/printer_multipage designs only)

object

printer_multipage designs only, where it replaces elements: a multipage design is one document with no formats, so content is addressed per page. Ignored on every other design type. The dedicated multipage PDF operation takes the same field and is the clearer choice for print output; this one exists so a printer_multipage design can also be driven through the generic async endpoint.

original_visual_id
string <uuid>

UUID of the original visual this generation is based on.

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "generation_request_id": "3f2a9c18-6b1e-4f4a-9a75-2b6d0e1c8f77"
}

Generate a multi-page PDF (asynchronous)

Asynchronously generate a multi-page print-ready PDF from a printer_multipage design. Each page is defined by a pages dictionary entry, allowing different element overrides per page — ideal for brochures, catalogs, and variable data printing.

Provide a callback_url to receive a webhook when the PDF is ready, or poll GET /generation-request/{generationRequestId} for status.

You can find available formats and elements by calling GET /designs/{designId}.

Authorizations:
api_key
path Parameters
designId
required
string <uuid>

Unique identifier (UUID) of the design. A value that is not a UUID does not match the route and answers 404 with id: endpoint_not_found.

Request Body schema: application/json
required
required
object (Pages)

Per-page element overrides, keyed by page identifier (page_1 … page_N). Each value is a dictionary of element overrides for that page, in the same shape as elements on every other generation endpoint — root plus any layer of that page, keyed by layer name. This endpoint is asynchronous, so its image layers accept the AI properties too.

callback_url
string <uri>

The url that will be called once generation of your PDF is done.

We will do a POST request of the NewBannerBatch event on this URL with a JSON payload.

object

Print output options

original_visual_id
string <uuid>

UUID of the original visual this generation is based on.

Responses

Request samples

Content type
application/json
{
  • "pages": {
    },
  • "callback_url": "http://example.com",
  • "print": {
    },
  • "original_visual_id": "efce0750-ba21-49c4-be6f-4e6125cdc6bb"
}

Response samples

Content type
application/json
{
  • "generation_request_id": "06399fcd-0c21-47da-bd9b-1e653e0453e8",
  • "version": "v2026-08-17"
}

Poll async generation request status

Poll the status of an asynchronous generation request. Use this endpoint to track the progress of a batch generation operation started with the async generate endpoint. The result is available for 7 days after generation.

Authorizations:
api_key
path Parameters
generationRequestId
required
string <uuid>

Unique identifier (UUID) of the generation request

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "is_finalized": true,
  • "banners": [
    ],
  • "errors": [
    ],
  • "version": "v2026-08-17"
}

Authentication

Every request is authenticated with an x-api-key header carrying a key generated from your Workspace settings.

Use POST /auth to confirm a key works and to find out which workspace it belongs to. Any problem with the key answers 401 — unknown, revoked, or on a plan that does not include API access. The id in the body distinguishes them: api_access_denied is the plan case, unauthorized covers the rest.

Use a workspace key, not a user key. A user-scoped key exists to confine a credential to the design plugins: it reaches only POST /auth, GET /fonts and the plugin endpoints, and every other operation in this reference answers 401, whatever the plan. It is not plan-checked either — so it answers 200 on /auth even where a workspace key on the same plan would answer 401 api_access_denied. If a key passes /auth but 401s everywhere else, that is why: it is a user key, and it was never meant to drive the API.

Verify an API key

Check that an API key is usable, and find out which workspace it belongs to.

This is the endpoint to call when you want to confirm a key works — it runs the whole authentication path and answers 200 only if all of the following hold:

  • the key exists,
  • it is still active (a revoked key answers 401),
  • and, for a workspace key, the plan includes API access (401 api_access_denied otherwise).

The plan check is skipped for a user-scoped key, which therefore answers 200 here whatever the plan. That is not a loophole: a user key is confined to the plugin surface, so a 200 from this endpoint means "this key is live", not "this key can generate". If you need to know whether a key can drive the API, call a real endpoint with it.

Every failure is a 401, whether the key is unknown, revoked, or on a plan without API access. This endpoint does not answer 403.

The response carries the workspace name, so a caller holding several keys can tell which one it has. Integration platforms use it to label a connection.

Do not use GET /ready to test a key. It is a service health check and is exempt from authentication, so it answers 200 regardless of the key you send — including one that has been revoked.

Takes no request body. It is a POST for historical reasons; live integrations depend on the current shape, so it will not be changed.

Authorizations:
api_key

Responses

Request samples

curl -X POST -H "x-api-key: $ABYSSALE_API_KEY" \
  https://api.abyssale.com/auth
# {"company":"Acme Inc.","version":"v2026-08-17"}

Response samples

Content type
application/json
{
  • "company": "Acme Inc.",
  • "version": "v2026-08-17"
}

Fonts

Retrieve the fonts available in your Abyssale workspace. Fonts can be applied to text and button elements during image generation. Two types are supported: Google Fonts (free, always available) and custom fonts uploaded to your workspace.

Reference a font by its ID when overriding the font property in a generation request.

List available fonts

Retrieve all fonts available in your Abyssale workspace, including Google Fonts and any custom fonts you have uploaded. Use a font's id to override the font on text or button elements in a generation request.

Authorizations:
api_key
query Parameters
type
string
Enum: "google" "custom"

Filter fonts by type. Omit to return both Google and custom fonts. An unknown value is ignored and the full unfiltered list is returned — this never answers 400 (long-standing behavior existing integrations rely on).

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Exports

Export a collection of previously generated banners as a single ZIP archive. Useful for downloading bulk assets for offline use, sharing with creative teams, or archiving a campaign's outputs. The export is processed asynchronously — provide a callback URL to receive a webhook notification when the archive is ready.

Banner export archive ready Webhook

An asynchronous banner export has completed. The ZIP archive containing all requested banners is available at the provided archive_url.

Request Body schema: application/json
event_type
string
Value: "NEW_EXPORT"

Identifies the event — every webhook payload carries this field.

export_id
string <uuid>

ID of the export

archive_url
string <uri>

The zip public URL

requested_at
number

Timestamp at which the export has been requested

generated_at
number

Timestamp at which the export has been done

Responses

Request samples

Content type
application/json
{}

Export generated assets as a ZIP archive (asynchronous)

Asynchronously package a set of previously generated assets — any output type (JPEG, PNG, WEBP, AVIF, PDF, MP4, GIF, HTML5 bundle) — into a single ZIP archive for download. Provide a callback_url to receive a webhook notification when the export is ready, containing the archive URL.

Authorizations:
api_key
Request Body schema: application/json
required
ids
required
Array of strings <uuid> non-empty [ items <uuid > ]

An array of string that corresponds to the banners IDs you would like to export.

callback_url
string <uri>

The url that will be called once the generation of your export is done.

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "export_id": "64238d01-d402-474b-8c2d-fbc957e9d290",
  • "version": "v2026-08-17"
}

Files

Retrieve metadata and download URLs for generated files (images, videos, GIFs, HTML5 banners, and PDFs). Listen to webhook events to be notified in real time when a file or batch generation completes.

File generated Webhook

A new file (image, video, GIF, HTML5 banner, or PDF) has been generated and is ready to download.

Subscribed in the dashboard, not per request. Unlike the other two events, this one has no callback_url field on any endpoint — it is configured once for the workspace and then fires for every file generated, including files produced outside the API.

Delivery, retries and the event_type convention are described under Webhooks.

Request Body schema: application/json
event_type
string
Value: "NEW_BANNER"

Identifies the event — every webhook payload carries this field.

id
required
string <uuid>
version
integer

Version number of the generated file — an integer counter, NOT the API version. A banner response reports this counter; the vYYYY-MM-DD stamp other responses carry never appears here.

sharing_id
string <uuid>

Identifier used for sharing this generated file.

required
object (File)
object (Format)
object

The design this file was generated from.

object

The design's project — present when the design has one.

object
Deprecated

Deprecated — same information as file.type / file.url.

edit_url
string

Platform edit URL — only for visuals bookmarked or downloaded in the platform.

view_url
string

Platform view URL — only for visuals bookmarked or downloaded in the platform.

object

Present when the visual carries a review status.

Responses

Request samples

Content type
application/json
{
  • "event_type": "NEW_BANNER",
  • "id": "64238d01-d402-474b-8c2d-fbc957e9d290",
  • "version": 1,
  • "sharing_id": "5fcec999-2bfb-4dd7-ba38-2d9e16c49149",
  • "file": {},
  • "format": {
    },
  • "template": {
    },
  • "project": {
    },
  • "image": {},
  • "edit_url": "string",
  • "view_url": "string",
  • "visual_status": {
    }
}

Batch generation completed Webhook

An asynchronous batch generation request has completed. Contains the full list of generated files and any per-format errors. Triggered by POST /async/banner-builder/{designId}/generate.

Request Body schema: application/json
event_type
string
Value: "NEW_BANNER_BATCH"

Identifies the event — every webhook payload carries this field.

generation_request_id
required
string <uuid>

Unique identifier (UUID) of the batch generation

required
Array of objects (Banner)

List of generated images

Array of objects

If any errors occur, they will be listed under that property

Responses

Request samples

Content type
application/json
{
  • "event_type": "NEW_BANNER_BATCH",
  • "generation_request_id": "06399fcd-0c21-47da-bd9b-1e653e0453e8",
  • "banners": [
    ],
  • "errors": [
    ]
}

Get generated file

Retrieve the metadata and download URLs for a previously generated file (image, video, GIF, HTML5 banner, or PDF). Returns both the S3 storage URL and a CDN-hosted URL suitable for embedding directly in websites or emails.

Authorizations:
api_key
path Parameters
bannerId
required
string <uuid>

Unique identifier (UUID) of the file

Responses

Response samples

Content type
application/json
{
  • "id": "64238d01-d402-474b-8c2d-fbc957e9d290",
  • "version": 1,
  • "sharing_id": "5fcec999-2bfb-4dd7-ba38-2d9e16c49149",
  • "file": {},
  • "format": {
    },
  • "template": {
    },
  • "project": {
    },
  • "image": {},
  • "edit_url": "string",
  • "view_url": "string",
  • "visual_status": {
    }
}

Projects

Projects let you organize your designs into logical groups — for example by campaign, client, or content type. All designs must belong to a project to be accessible via the API. Use projects to scope design retrieval and keep large design libraries manageable.

List projects

Retrieve all projects in your workspace. Projects group designs into logical collections. Only designs that belong to a project are accessible via the API.

Authorizations:
api_key

Responses

Response samples

Content type
application/json
[
  • {
    },
  • {
    }
]

Create a project

Create a new project to organize your designs. Assign designs to this project in the Abyssale editor to make them accessible via the API.

Authorizations:
api_key
Request Body schema: application/json
required
name
required
string [ 2 .. 100 ] characters

Name of the project

Responses

Request samples

Content type
application/json
{
  • "name": "Summer Campaign 2024"
}

Response samples

Content type
application/json
{
  • "id": "cb2c4add-4867-11f0-96f2-0a00d9eb8f78",
  • "name": "Summer Campaign 2024",
  • "created_at_ts": 1749827107
}

Workspace Templates

Workspace templates are master designs shared across your organization. Duplicate them into any project to create editable copies — useful for spinning up campaign variants, onboarding new clients, or maintaining brand-consistent starting points. The duplication is processed asynchronously; poll the status endpoint to track progress.

List all workspace templates

Retrieve the organisation-level master designs shared across your workspace.

A workspace template lives in a category (category_id), not a project — it belongs to the organisation until you duplicate it into a project with POST /workspace-templates/{companyTemplateId}/use. Designs, which do live in a project, are listed by GET /designs; the two listings never overlap.

Items have the same shape as a design, except the grouping fields are named category_id / category_name.

Authorizations:
api_key
query Parameters
category_id
string <uuid>

Unique identifier (UUID) of a workspace category. List the available ids with GET /workspace-template-categories. Categories are optional, so this filter only ever returns categorised templates — omit it to include those at the workspace root.

type
string
Enum: "static" "printer" "animated" "printer_multipage"

Filter workspace templates by one of these types: static, animated, printer, printer_multipage. An unknown value is ignored and the full unfiltered list is returned — this never answers 400 (long-standing behavior existing integrations rely on).

Responses

Response samples

Content type
application/json
[
  • {
    }
]

List workspace template categories

The categories that group your workspace templates. Use an id from here as the category_id filter on GET /workspace-templates.

This is the workspace-template counterpart of GET /projects, which groups designs.

Authorizations:
api_key

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Duplicate a workspace template into a project

Copy a shared workspace template into one of your projects, creating an editable design instance. The duplication is asynchronous — use the returned duplication_request_id to poll for completion via GET /design-duplication-requests/{duplicateRequestId}.

Authorizations:
api_key
path Parameters
companyTemplateId
required
string <uuid>
Example: 0c967bd0-4137-4690-ad70-249aa021c68b

Unique identifier (UUID) of the workspace template to duplicate

Request Body schema: application/json
required
project_id
required
string <uuid>

Target project ID where the template will be duplicated

name
string [ 2 .. 100 ] characters

Optional custom name for the duplicated template

Responses

Request samples

Content type
application/json
Example
{
  • "project_id": "d59adee9-4867-11f0-96f2-0a00d9eb8f78"
}

Response samples

Content type
application/json
{
  • "duplication_request_id": "40c32a4e-4869-11f0-96f2-0a00d9eb8f78"
}

Poll template duplication request status

Poll the status of a template duplication request. Use this endpoint to track the progress of an asynchronous duplication operation.

Authorizations:
api_key
path Parameters
duplicateRequestId
required
string <uuid>
Example: 40c32a4e-4869-11f0-96f2-0a00d9eb8f78

Unique identifier (UUID) of the duplication request

Responses

Response samples

Content type
application/json
Example
{
  • "request_id": "40c32a4e-4869-11f0-96f2-0a00d9eb8f78",
  • "status": "COMPLETED",
  • "created_at_ts": 1749827734,
  • "completed_at_ts": 1749827736,
  • "errored_at_ts": null,
  • "target_project": {
    },
  • "designs": [
    ]
}

Design Import

Alpha. This API is in Alpha. The contract may change without notice and imports created with it are not guaranteed to remain compatible. It is not covered by the deprecation policy. This applies to every operation in this section.

Create a design from a JSON structure — static, animated, printer or printer_multipage. The import produces a design in a project by default; pass target.kind: "workspace_template" for a reusable workspace blueprint instead.

  1. POST /designs/import/json — submit the structure. Returns an import id and a flat uploads[] array of presigned S3 POSTs, one per image/logo layer whose src is a filename.
  2. Upload assets — POST each file to its presigned URL with the returned fields.
  3. PUT /designs/import/json/{importId} — validate and queue the import.

No upload needed when every src is a public URL or data URI: the POST returns the import already QUEUED with an empty uploads[], and there is no step 2 or 3.

Dry run: POST …?validate_only=true runs schema and payload cross-field validation without creating anything — 204 on success, the error contract on failure. Three checks need I/O and are skipped, so a 204 does not guarantee the real POST succeeds: unknown_font, project_not_found and unreachable_src.

Poll GET /designs/import/json/{importId} for status. It carries next_check_after_ms (stop polling when it is null), structured warnings[], and a structured error when status is FAILED. A failed import is still HTTP 200 — branch on status, not on the status code. Thumbnails are rendered for you; the payload has no preview image field (preview_image is unknown_field).

Errors use the same envelope as the rest of the API — a top-level id and message, plus an errors array locating each problem. A rejected payload usually fails in several places at once, which is what errors is for; id is the shared code when every entry agrees and invalid_payload when they differ:

{
  "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 }
  ]
}

Round trip: GET /designs/{designId}/as-import returns any existing design — every type, multipage included — in the exact shape this endpoint accepts.

Per design type — full rules in the guides linked above:

Type Authored in Colors Key limits
static px, integer hex format 1–5000 px
animated px, integer hex required animation.duration 1–60 s; format within a 1920×1200 box in either orientation
printer one top-level unit (mm|in), float geometry, font_size in pt 0.48–240 cmyk()/cmyka(); a solid hex is converted with a color_converted warning; gradients are rejected layer assets 500 MB presigned
printer_multipage as printer, print setup declared once at the top level as printer pages 1–30, each 1–80 layers

Data URIs are capped at 2 MB decoded on every type. Presigned layer assets: 20 MB static, 100 MB video / 25 MB audio / 20 MB image on animated, 500 MB on print.

List imports

Alpha. This API is in Alpha. The contract may change without notice and imports created with it are not guaranteed to remain compatible. It is not covered by deprecation policy.

List this company's JSON template imports, newest first.

Use this to recover an import whose id you no longer have. Because the flow is POST → upload → PUT, a client that loses the id between those steps leaves behind an import it cannot otherwise resume or inspect; listing is how you find it again.

Rows are summaries. The unbounded warnings[] array and the error object are served by GET /designs/import/json/{importId} — a row carries warning_count instead, and a links.status pointing at that endpoint.

status filters on the effective status, matching what the item endpoint reports: an import whose presigned upload window has closed counts as FAILED, not WAITING_FOR_VALIDATION.

Lists JSON imports exclusively — imports created through other channels are not returned.

No cursor. Results are capped by limit; narrow with created_after / created_before to reach imports beyond the cap.

Authorizations:
api_key
query Parameters
status
string
Enum: "WAITING_FOR_VALIDATION" "QUEUED" "PROCESSING" "DONE" "FAILED"

Return only imports whose effective status matches.

created_after
integer

Unix timestamp (seconds). Return imports created at or after this moment.

created_before
integer

Unix timestamp (seconds). Return imports created at or before this moment.

limit
integer [ 1 .. 200 ]
Default: 50

Maximum number of imports to return.

Responses

Response samples

Content type
application/json
[]

Create a design from JSON

Alpha. This API is in Alpha. The contract may change without notice and imports created with it are not guaranteed to remain compatible. It is not covered by deprecation policy.

Submit a template structure as JSON. Returns an import ID, a links block, and a flat uploads[] array of presigned S3 POSTs — one for every asset given as a bare filename: image/logo src, video/audio src on animated designs, and a qrcode layer's icon_src. When every asset is a public URL or a data URI, the import is returned already in QUEUED with an empty uploads[] — no PUT call is needed.

The payload carries no preview image: design thumbnails are generated by the backend after import (every format is rendered and the first visual becomes the thumbnail; the import reports PROCESSING while previews render).

Dry run: pass ?validate_only=true to validate the payload without creating an import. Runs the full schema and all payload-only structural validators (conflicting filename sources, data-URI validity and decoded-size caps, TIFF type constraints, video/audio extensions, animated keyframes, qrcode icons). Excluded: unknown_font, project_not_found and unreachable_src. Returns 204 No Content on success.

Plan entitlement. A design type your plan does not include is refused here rather than at generation time, so a workspace never ends up owning a design it cannot render:

type Requires Available from
static any plan
animated MP4 Pro
printer, printer_multipage Printable PDF Suite

These are the same entitlements that gate generating the corresponding assets, so a plan that can render an animated design can also import one. See abyssale.com/pricing.

A workspace without the feature gets 429 feature_not_in_plan. The check also applies to ?validate_only=true, so a dry run never returns 204 for a design the real call refuses.

Authorizations:
api_key
query Parameters
validate_only
boolean
Default: false

When true, runs schema and cross-field validation and returns 204 No Content without creating an import row or generating presigned URLs.

strict
boolean
Default: false

When true, a text layer that cannot fit its box even at min_font_size fails the import instead of importing with a text_fit_failed warning.

It never changes this response. Text fitting is measured while the design is built, long after this call has answered, so strict cannot produce a 400. The import is accepted exactly as it would be otherwise, and the difference shows up when you poll: status: "FAILED" with error.code: "text_fit_failed" — delivered as HTTP 200, like every other terminal import state. Without strict the same condition appears as a warnings[] entry and the import succeeds.

Request Body schema: application/json
required
name
required
string [ 1 .. 100 ] characters
type
required
string
Enum: "static" "printer" "animated" "printer_multipage"

Design type. static = single image; printer = single-page print PDF; animated = animated/video design; printer_multipage = multi-page print PDF. Printer payloads are authored in physical units — the design declares unit (mm | in) once at the top level, geometry is floats in that unit, font sizes are pt, and colors are stored as cmyk (a solid hex is converted with a color_converted warning; gradients are rejected). Animated payloads are px-authored like static and require a top-level animation object; they may use video/audio layers and per-layer animation timing/tweens.

Muting is spelled differently on each of the three surfaces, and the type changes too. Importing takes muted (a boolean, on the video/audio layer properties); reading a design returns it as the attributes video_muted / audio_muted (booleans); overriding it at generation time takes audio_muted as a number 0–1, where true/false answer 400. Convert deliberately when moving a value between them. Multipage payloads (printer_multipage) drop formats/layers, declare the print setup once at the top level (unit/width/height required, bleed_size/safe_size/dpi optional), and carry a top-level ordered pages array (1–30 pages); each page is a self-contained print page (own background_color + layers, inheriting the document-wide print setup) that inherits every per-layer printer rule. No shared layers exist — a repeated element is included per page.

object

Where the import lands. An import creates a design — the thing you can generate from — so this defaults to { "kind": "design" } and project_uuid is required: a design lives in a project, and one without a project is not returned by GET /designs. Omitting target entirely therefore fails with missing_required at target.project_uuid.

Pass kind: workspace_template for the other variant: a reusable blueprint in the workspace library, which has no project and must be duplicated into one before you can generate from it. Only designs are addressable via GET /designs/{uuid}/as-import.

Array of objects (DesignImportFormat) [ 1 .. 20 ] items

List of formats (1-20). Required on static/printer/animated; omit on printer_multipage (use pages instead). A count outside 1-20 is rejected with out_of_range at formats. background_color is required on each format. There is no preview-image field — thumbnails are rendered for you (a preview_image key is rejected as unknown_field).

Array of objects (DesignImportLayer) [ 1 .. 80 ] items

Required on static/printer/animated; omit on printer_multipage (each page carries its own flat layers). 1-80 layers - an empty array is rejected with out_of_range at layers (expected: {min: 1, max: 80}), since required enforces key presence and not non-emptiness. Layers defined once at the top level. Each layer contains layout and properties keyed by format name. A "default" key may be used in either dict — it is merged under each per-format entry before validation, so overrides only need to specify the fields that differ.

unit
string
Enum: "mm" "in"

Print designs only (printer and printer_multipage) — required. Physical authoring unit for everything in the document: format/page size, bleed and safe sizes, and every layer's geometry in every format. Declared exactly once, because a print design has one ruler — it does not change between its formats. Missing → missing_required at unit; present on static/animatedunsupported_for_type. A format carrying its own unit is rejected with unknown_field.

width
number > 0

printer_multipage designs only — required. Page width as a float in unit, shared by every page. Its pixel equivalent at 300 DPI must stay within 1–20000 (out_of_range at width).

height
number > 0

printer_multipage designs only — required. Page height as a float in unit, shared by every page. Same 1–20000 px-equivalence bound, reported at height.

bleed_size
number >= 0

printer_multipage designs only. Optional bleed size as a float in unit; presence enables bleed on every page.

safe_size
number >= 0

printer_multipage designs only. Optional safe-zone size as a float in unit; presence enables the safe zone on every page.

dpi
integer [ 10 .. 300 ]

printer_multipage designs only. Optional global render DPI for the whole document. Because the document has a single page size there is exactly one area-based ceiling min(300, floor(72*sqrt(225000000/(w_pt*h_pt)))); a higher value is rejected with out_of_range at dpi (carrying expected: {max: <ceiling>} and received). Omitted ⇒ the backend computes that same ceiling as the default, identically for every page. On printer designs dpi is a per-format field instead.

Array of objects (DesignImportPage) [ 1 .. 30 ] items

printer_multipage designs only (replaces formats + top-level layers; forbidden on other types). Ordered array of 1–30 self-contained print pages — array order is the page order (the backend names them page_1 … page_N). A count outside 1–30 → out_of_range at pages. All pages share one size, so the print setup (unit/width/height/bleed_size/safe_size/dpi) is declared once at the top level; a page object carries only background_color and a flat layers array (one format per page).

object

Animated designs only — required when type is animated, rejected with unsupported_for_type on static/printer. The design's timeline.

Responses

Request samples

Content type
application/json
{
  • "name": "Summer sale",
  • "type": "static",
  • "target": {
    },
  • "formats": [
    ],
  • "layers": [
    ]
}

Response samples

Content type
application/json
{}

Get import status

Alpha. This API is in Alpha. The contract may change without notice and imports created with it are not guaranteed to remain compatible. It is not covered by deprecation policy.

Poll the status of a JSON template import. Scoped to the caller's company.

Returns next_check_after_ms (suggested delay before the next poll, null on terminal states), a stable append-only warnings[] array, and a structured error object when status is FAILED.

Expired upload windows. An import that reaches WAITING_FOR_VALIDATION and is never validated before its presigned upload window closes is reported as terminal: status: "FAILED", next_check_after_ms: null, no validate link, and error: {path: "uploads", code: "missing_assets", ...}. This is derived at read time from the import's creation timestamp — the GET performs no write, so the import keeps its original state. A poller should therefore always drive off terminal states rather than waiting indefinitely.

Authorizations:
api_key
path Parameters
importId
required
string <uuid>

Import UUID returned by the POST endpoint.

Responses

Response samples

Content type
application/json
Example
{}

Validate an import and queue it

Alpha. This API is in Alpha. The contract may change without notice and imports created with it are not guaranteed to remain compatible. It is not covered by deprecation policy.

Call this after every entry of uploads[] has been POSTed to S3. The edge API performs a pre-flight HEAD on every expected key. If any are missing, it returns 422 missing_assets with the list of unfulfilled targets.

On success, the import status transitions to QUEUED and backend processing picks it up. PUT is idempotent on QUEUED rows. PUT on a PROCESSING, DONE, or FAILED row returns 409 template_import_already_processed.

Authorizations:
api_key
path Parameters
importId
required
string <uuid>

Import UUID returned by the POST endpoint.

Responses

Response samples

Content type
application/json
{}

Export a design as an import payload

Alpha. The contract may change without notice and imports created with it are not guaranteed to remain compatible. Not covered by the deprecation policy.

Return an existing design — any type, printer_multipage included — serialized into the exact shape POST /designs/import/json accepts. Useful as a worked example for an agent, or as a round-trip regression test (as-importPOSTas-import should diff empty).

src values are Abyssale-hosted https:// URLs, so no asset needs re-uploading. The response is validated against the import schema before it is returned: a design holding anything the schema cannot express answers 400 not_round_trippable with structured details.

Stored state the contract has no field for is dropped and named in warnings[], never folded into a field that means something else — a re-import reproduces the design minus what was reported, never different artwork.

Values outside the import bounds are clipped and warned about rather than failing the export. What cannot degrade fails instead: a gradient on a print design, a printer_multipage whose pages disagree on their unit (each page's geometry is expressed in its own unit, so one ruler cannot describe them), an animated format outside the 1920×1200 box, and an export left with no layers.

Rate limit: shares the write budget with POST /designs/import/json — 60 requests/minute and 1 200/hour across both, so a round trip costs two calls and 1 200/hour is 600 export–edit–import cycles. See Rate limits.

Authorizations:
api_key
path Parameters
designId
required
string <uuid>

Unique identifier (UUID) of the design. A value that is not a UUID does not match the route and answers 404 with id: endpoint_not_found.

Responses

Response samples

Content type
application/json
{
  • "payload": {
    },
  • "warnings": [
    ],
  • "version": "string"
}

Dynamic Images

Dynamic images are live image URLs that render on-the-fly based on query parameters. Once created for a design, the URL can be embedded anywhere — emails, websites, social posts — and customized with different text, colors, or images per use case, without additional API calls. Ideal for personalization at scale.

The one endpoint below mints the URL. Everything after that is the URL itself, which is served from img.abyssale.com and is not part of this API — so its grammar is documented here rather than under an operation.

https://img.abyssale.com/{dynamicImageId}[/{formatNameOrUid}]?{overrides}

{dynamicImageId} is the id from the creation response — not the design id. The dynamic_image_url that response returns already includes the format segment, so you can append a query string to it directly. Omit the format segment and the design's first format is rendered; give a format name or its uid (prefer uid — it survives a rename). An unknown format answers 404.

The URL is public. No API key, no signature: anyone holding it can render variants. That is the point — it goes in an email — but it means the design, not the URL, is the trust boundary. enable_rate_limit on the creating call caps each visitor at 5 distinct variants per 24 h.

Overrides. Each query parameter names a layer of the design:

Form Syntax Example
Long layer_name.property_name=value title.color=%23FF0000
Short layer_name=value — the property is inferred from the layer's type title=Welcome

The short form sets payload on text / button / qrcode layers, image_url on image / logo, and rating_score on rating. Every property listed under Element Properties is addressable in the long form.

Five rules worth knowing before you generate URLs programmatically:

  • URL-encode every value. # becomes %23; spaces and nested URLs must be escaped. An empty value is a hard 400.
  • A long-form key must contain exactly one dot. More dots and the parameter is silently ignored.
  • A short-form key naming a layer that is not on the rendered format is silently dropped; a long-form key naming an unknown property answers 400.
  • A repeated key keeps the first value.
  • A literal \n in a text value becomes a real line break.

Note the asymmetry: unknown layers fail silently, unknown properties fail loudly. Render the URL once and look at it before wiring it into a campaign.

Create a dynamic image URL

Creates a dynamic image URL for a given design (static designs only). Only one dynamic image is allowed per design — subsequent calls return the existing dynamic image (200 instead of 201). enable_rate_limit limits each visitor (IP + browser fingerprint) to 5 distinct variants per 24 h. enable_production_mode switches from test mode (variants saved to the workspace, server-side cache, 10 req/s per image) to production mode (built for scale: unlimited, unthrottled generation — only workspace credits and bandwidth apply; variants not saved, every request rendered fresh).

Authorizations:
api_key
path Parameters
designId
required
string <uuid>

Unique identifier (UUID) of the design. A value that is not a UUID does not match the route and answers 404 with id: endpoint_not_found.

Request Body schema: application/json
required
enable_rate_limit
boolean
Default: false

Enable API rate limiting for this dynamic image

enable_production_mode
boolean
Default: false

Enable Production mode for this dynamic image

Responses

Request samples

Content type
application/json
{
  • "enable_rate_limit": true,
  • "enable_production_mode": true
}

Response samples

Content type
application/json
{}
Download spec