openapi: 3.0.0
info:
  title: Ad Delayer API Reference
  version: ''
  description: >
    Below you will find the technical specifications, request schemas, and
    response models for our endpoints.


    Choose an endpoint from the sidebar to view code samples and run test
    requests.
tags:
  - name: v2 endpoints
    description: |
      Endpoints that are part of BRIA API version 2.
externalDocs:
  description: Register and get API Access
  url: https://platform.bria.ai/organization-management/api-keys
servers:
  - url: https://engine.prod.bria-api.com/v2
paths:
  /ads/delayer:
    post:
      summary: Ad Delayer
      tags:
        - v2 endpoints
      operationId: delayer-ad
      description: >
        Turn a flat ad image into editable layers — a background plate, product
        and logo cutouts, live text with typography, and vector shapes —
        returned as one structured JSON document. Trained exclusively on
        licensed data for safe commercial use.


        Latency is on average under 1 minute.


        **Input Assumptions**


        The input is assumed to be a **flat ad**. Valid images that aren't ads —
        an ordinary photograph, say — are out of scope, and the result isn't
        guaranteed.


        Fields the API doesn't recognize are currently ignored rather than
        rejected, so check your spelling — a mistyped parameter name fails
        silently and you get default behavior.


        Submissions are not idempotent — a retried POST is a new job. Failed
        jobs are never billed.


        **Response Handling**


        Submitting returns a `request_id` and a `status_url` to poll. On
        completion the status body carries a **pointer**, not the layers: fetch
        `result.url` to get the layer document, then each image layer's
        `asset_path` separately.


        One caveat when reporting a problem: the `request_id` in a response body
        identifies that response, not your job. The durable handle is the id
        inside the `status_url` you got at submit — quote that.


        **The Layer Document**


        ```json

        {
          "canvas": { "width": 1000, "height": 1500 },
          "font_stylesheets": ["https://fonts.googleapis.com/css2?family=Inter…"],
          "layers": [ … ]
        }

        ```


        | Field | Type | Description |

        |---|---|---|

        | `canvas` | object | `width` / `height`, matching the input image. |

        | `font_stylesheets` | `string[]` | Returned in the result. Google Fonts
        stylesheet URLs covering every font referenced by text layers. Load
        these before rendering. |

        | `layers` | array | Every layer in the ad. **Sort by `z_order` before
        rendering** — array order is not guaranteed to match paint order. |


        On every layer:


        | Field | Type | Description |

        |---|---|---|

        | `id` | string | Result-scoped: `background_1`, `text_3`, `image_1`,
        `shape_1`, plus the literal `canvas_background`. A copy layer arrives as
        a pair, suffixed `_font` and `_svg` — `text_3_font` beside `text_3_svg`.
        |

        | `type` | string | `image`, `vector`, or `text`. |

        | `subtype` | string | `background`, `logo`, `product`, `primary_copy`,
        `secondary_copy`, `cta`, `compliance`, `icon`, `decorative`. |

        | `bbox` | object | `x`, `y`, `width`, `height` in canvas pixels. |

        | `z_order` | integer | Paint order, from 0 upward with no gaps; lowest
        paints first. Sort on this — don't rely on array order. Each half of a
        copy pair takes its own number, so after dropping hidden layers, gaps in
        the visible sequence are expected. |

        | `hidden` | boolean | Present and `true` only on the half of a copy
        pair the mode did not choose. **Skip those layers.** Absent means
        visible — it is never `false`, so test for `hidden === true`. |


        Image layers:


        | Field | Type | Description |

        |---|---|---|

        | `asset_path` | string | Hosted image. Cutouts are transparent. PNG,
        except on the `_svg` half of a copy pair, which is an SVG of the traced
        letterforms. |

        | `image_fit.object_fit` | string | `cover` or `contain`, with CSS
        `object-fit` semantics. |

        | `image_fit.object_position_x`<br>`image_fit.object_position_y` |
        string | Optional. Where the image sits in its box when the fit leaves
        slack — `left`/`center`/`right` and `top`/`center`/`bottom`. Absent
        means centered. |


        Vector layers:


        | Field | Type | Description |

        |---|---|---|

        | `style.background_color` | string | Solid fill, `#RRGGBB`. |

        | `style.background_gradient` | object | `stops[]` of `{offset, color}`,
        plus `kind` and `angle_deg`. Stop colors may carry an 8-digit
        `#RRGGBBAA` alpha suffix. |

        | `style.border_radius_px` | number | Corner radius, when present. |

        | `style.box_shadow` | object | Optional. `offset_x_px`, `offset_y_px`,
        `blur_px`, `spread_px`, `color` and `inset` (boolean), with CSS
        box-shadow semantics. |


        Vector layers carry no shape enum — treat them as boxes with a fill.


        Text layers:


        | Field | Type | Description |

        |---|---|---|

        | `text` | string | Live, editable content. Explicit line breaks are
        `\n` and are preserved when you re-render. |

        | `text_style.color` | string | `#RRGGBB`. |

        | `text_style.font_family` | string | A CSS font stack — nearest match
        first, fallbacks inline. |

        | `text_style.font_weight` | integer | 100–900. |

        | `text_style.font_size_px` | number | Pixels. |

        | `text_style.letter_spacing_px` | number | Pixels. Omitted when zero. |

        | `text_style.line_height` | number | Unitless multiplier over font
        size. |

        | `text_style.text_align` | string | Alignment of the text itself:
        `left`, `center`, `right`. Optional. |

        | `text_style.align_x` / `align_y` | string | Alignment of the block
        inside its box, as CSS flexbox values: `flex-start`, `center`,
        `flex-end`. |

        | `text_style.no_wrap` | boolean | `true` forbids wrapping inside the
        box. |

        | `text_style.uppercase` / `underline` / `italic` | boolean | Style
        flags. |


        <details>


        <summary style="cursor: pointer; color: #007bff; font-weight:
        bold;">Click to view a real copy pair (JSON, text_mode: svg)</summary>


        ```json

        [
          {
            "id": "text_5_svg",
            "type": "image",
            "subtype": "primary_copy",
            "bbox": { "x": 150.0, "y": 409.05, "width": 764.0, "height": 139.95 },
            "z_order": 8,
            "asset_path": "https://temp.bria.ai/results/860f1a2b73f847e59f284d6f860f2ddb/text_5.svg",
            "image_fit": { "object_fit": "contain" }
          },
          {
            "id": "text_5_font",
            "type": "text",
            "subtype": "primary_copy",
            "bbox": { "x": 150.0, "y": 409.05, "width": 764.0, "height": 139.95 },
            "z_order": 9,
            "hidden": true,
            "text": "40% OFF*",
            "text_style": {
              "color": "#FFFFFF",
              "font_family": "'Montserrat', 'Inter', 'Helvetica Neue', Arial, sans-serif",
              "font_weight": 900,
              "font_size_px": 149.58,
              "letter_spacing_px": -1.93,
              "line_height": 1.0,
              "align_x": "flex-start",
              "align_y": "center",
              "no_wrap": true,
              "uppercase": false,
              "underline": false,
              "italic": false
            }
          }
        ]

        ```


        With `text_mode: font` the same pair returns with `hidden: true` on
        `text_5_svg` instead.


        </details>


        **The two halves of a copy layer**


        Copy comes back in both forms regardless of `text_mode`, so one result
        can be rendered either way and an editor can offer the choice without a
        second job. Each copy slot therefore produces two layers:


        | Layer | Type | Carries |

        |---|---|---|

        | `<slot>_svg` | `image` | `asset_path` to an SVG of the letterforms
        traced out of the reference ad. |

        | `<slot>_font` | `text` | `text` and `text_style` — live, editable, in
        the nearest matching font. |


        Both halves carry the copy's subtype (`primary_copy`, `cta`, and so on),
        share the same bbox, and take their own `z_order`.


        `text_mode` decides which one is live. The other carries `hidden: true`
        and must not be drawn — render both and the ad shows its copy twice.


        `svg` is best suited to plain, solid-color type where the original
        appearance must be preserved. Choose `font` when the copy will be
        edited, resized, or localized, and for text with fills, multiple colors,
        or overlapping elements — filled fonts are not supported in SVG and
        return a transparent layer.


        A wordmark logo — a brand written out in lettering, as against a
        pictorial mark — is paired the same way, except that both halves are
        `image` layers: `_svg` is the traced outline and `_font` is the raster
        crop (on a wordmark the `_font` suffix marks the fallback half, not a
        text layer). A logo is never typeset.


        Layers with nothing to pair, which is most of them, keep their plain id
        and never carry `hidden`.


        Two things worth doing rather than ignoring: skipping the hidden half
        saves an asset download per copy layer, and in `svg` mode nothing needs
        `font_stylesheets` at all, because no text layer is drawn (the field may
        also be omitted entirely when no webfont is needed, so read it
        defensively).


        **Rendering It Back**


        **Drop every layer with `hidden: true`, then sort the rest by
        `z_order`** — the array often arrives already in that order, but not
        always. Then absolutely position each layer at its `bbox`, inside a
        canvas-sized stage. Image layers become an `<img>` with the given
        `object-fit`; vector layers become a div with the fill; text layers
        become a flex box using `align_x` and `align_y`. Load `font_stylesheets`
        first, or your text metrics will be wrong.


        The examples above come from a full-resolution (enterprise) run, which
        is why the canvas exceeds 800 px.
      parameters:
        - in: header
          name: api_token
          schema:
            type: string
          required: true
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                attachments:
                  type: array
                  minItems: 1
                  maxItems: 1
                  items:
                    type: string
                  description: >-
                    Exactly one image — a publicly reachable URL, or a
                    `data:image/…;base64,…` URI. PNG, JPEG or WEBP, up to 10 MB
                    and 800 px per dimension (enterprise accounts have no
                    dimension cap). More than one entry is rejected. This
                    parameter is required.
                text_mode:
                  type: string
                  enum:
                    - svg
                    - font
                  default: svg
                  description: >-
                    Where each copy layer's glyphs come from. `svg` traces them
                    out of the reference ad as vector outlines, so the original
                    letterforms are kept exactly; `font` typesets them as live,
                    editable text in the nearest matching font. Either way both
                    forms come back and one is marked `hidden` — see **The two
                    halves of a copy layer**. The default is `svg` so the result
                    is faithful to the original by default; the editable half is
                    always present to switch to.
                webhook_url:
                  type: string
                  format: uri
                  description: >-
                    Optional URL for receiving the result via webhook when the
                    async job completes, with the same body as a status poll.
                    See [Webhooks](https://docs.bria.ai/webhooks).
              required:
                - attachments
            example:
              attachments:
                - >-
                  https://bria-image-repository.s3.amazonaws.com/images/c41b39ec1a69a5d3.jpg
              text_mode: svg
      responses:
        '200':
          description: >-
            Accepted (Asynchronous). You can track the progress and retrieve the
            final result using the Status Service. For more details, refer to
            the [Status Service](https://docs.bria.ai/status) section.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AsyncInitialResponse'
              example:
                request_id: 860f1a2b73f847e59f284d6f860f2ddb
                status_url: >-
                  https://engine.prod.bria-api.com/v2/status/860f1a2b73f847e59f284d6f860f2ddb
        '400':
          description: Bad request. No image supplied in `attachments`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized. Missing or invalid `api_token`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '413':
          description: Request body over the platform cap.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: >-
            Unprocessable Entity. More than one entry in `attachments`, an
            invalid value for a supported field, or an image over the 800 px
            per-dimension cap. Cap rejections carry the message *"Image exceeds
            800 px per dimension on this endpoint. Resize the source image, or
            use full-resolution as an enterprise user at Bria.ai/contact-us."*
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error:
                  code: 422
                  message: >-
                    The request was well-formed but was unable to be followed
                    due to semantic errors.
                  details: >-
                    ['Invalid Input -> only one image URL is supported per
                    request']
                request_id: 3a51f1c2f0de4b6c9a3e2b41a7c9d0e8
        '429':
          description: Request limit exceeded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        5XX:
          description: >-
            Internal Server Error. The image could not be fetched or decoded, or
            the pipeline failed. Submit again; if it persists, contact support
            and quote the `status_url` of the run.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /status/{request_id}:
    get:
      summary: Poll Job Status
      tags:
        - v2 endpoints
      operationId: get-ad-delayer-status
      description: >
        Status runs `IN_PROGRESS` to `COMPLETED` or a terminal error, and never
        moves backwards. Results and every referenced asset stay downloadable
        for **30 days** after the job reaches a terminal status.


        An unrecognized or expired id comes back as `"status": "UNKNOWN"` with
        **HTTP 200**, not a 404. Treat `UNKNOWN` as not-found and stop polling.


        On completion the body carries a **pointer**, not the layers: fetch
        `result.url` to get the layer document, then each image layer's
        `asset_path` separately. The layer document contract is documented on
        the Ad Delayer endpoint.


        Failures use the standard platform error envelope, where `error.code`
        repeats the numeric HTTP status and `details` carries the specifics.
      parameters:
        - in: header
          name: api_token
          schema:
            type: string
          required: true
        - in: path
          name: request_id
          schema:
            type: string
          required: true
          description: The id inside the `status_url` returned at submit.
      responses:
        '200':
          description: >-
            Current job status. Also returned for unrecognized or expired ids,
            as `UNKNOWN`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DelayerStatusResponse'
              examples:
                inProgress:
                  summary: In progress
                  value:
                    request_id: 860f1a2b73f847e59f284d6f860f2ddb
                    status: IN_PROGRESS
                completed:
                  summary: Completed
                  value:
                    request_id: 860f1a2b73f847e59f284d6f860f2ddb
                    status: COMPLETED
                    result:
                      status: completed
                      text: Constructed a layered ad.
                      url: >-
                        https://temp.bria.ai/results/860f1a2b73f847e59f284d6f860f2ddb/creation.json
                unknownId:
                  summary: Unknown or expired id
                  value:
                    request_id: 860f1a2b73f847e59f284d6f860f2ddb
                    status: UNKNOWN
        '401':
          description: Unauthorized. Missing or invalid `api_token`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  schemas:
    AsyncInitialResponse:
      type: object
      properties:
        request_id:
          type: string
        status_url:
          type: string
      required:
        - request_id
        - status_url
    DelayerStatusResponse:
      type: object
      properties:
        request_id:
          type: string
        status:
          type: string
          description: >-
            `IN_PROGRESS`, `COMPLETED`, or `UNKNOWN` (unrecognized or expired
            id, returned with HTTP 200), or a terminal error status. Never moves
            backwards.
        result:
          type: object
          description: >-
            Present when `status` is `COMPLETED`. A pointer to the hosted result
            — fetch `url` for the layer document, then each image layer's
            `asset_path` separately.
          properties:
            status:
              type: string
              example: completed
            text:
              type: string
              example: Constructed a layered ad.
            url:
              type: string
              description: The hosted layer document (JSON). Downloadable for 30 days.
      required:
        - request_id
        - status
    ErrorObject:
      type: object
      properties:
        code:
          type: integer
          example: 422
        message:
          type: string
        details:
          type: string
      required:
        - code
        - message
        - details
    ErrorResponse:
      type: object
      properties:
        error:
          $ref: '#/components/schemas/ErrorObject'
        request_id:
          type: string
          description: >-
            Identifies this response, not your job — the durable handle is the
            id inside the `status_url` you got at submit.
      required:
        - error
        - request_id
