Static Ads Lab
Guides

Figma sync

Round-trip an image ad through Figma using the Static Ads Lab plugin.

Static Ads Lab ships with a Figma plugin that lets you pull an image ad's JSON tree into Figma, edit it visually, and sync the result back to the API.

Prerequisites

Round-trip flow

sequenceDiagram
  participant App as Your app
  participant API as Static Ads Lab API
  participant Figma as Figma + plugin
  App->>API: POST /v1/image-ads
  API-->>App: completed { current_json_tree }
  Figma->>API: Plugin loads ad by ID
  API-->>Figma: current_json_tree + figma_render_url
  Note over Figma: User edits in Figma
  Figma->>API: POST /v1/image-ads/sync (items)
  API-->>Figma: 200 { results }
  Note right of API: render in the background
  App->>API: GET /v1/image-ads/:id/render-status
  API-->>App: rendered_tree_revision == tree_revision

Sync endpoint

The Figma plugin uses POST /v1/image-ads/sync to push edits back. Up to 10 ads per request:

await fetch("https://api.staticadslab.com/v1/image-ads/sync", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.SAL_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    items: [
      {
        id: "ia_...",
        current_json_tree: updatedTree, // { schemaVersion: 2, colorProfile: "SRGB", root }
        base_revision: 7, // optional: the tree_revision you loaded into Figma
        figma_render_base64: "iVBORw0KGgo...", // raw base64, no data: prefix
      },
    ],
  }),
});

Each item is handled on its own. An item with a current_json_tree is validated and saved as a new revision (tree_revision increments, and the previous state is kept as a version, iav_…), then the image renders in the background at 2×, the same way it does after a PATCH. Poll GET /v1/image-ads/:id/render-status until rendered_tree_revision equals tree_revision; a failed render shows in render_error and keeps the saved tree and the previous image. Send the optional per-item base_revision to get REVISION_CONFLICT for that item instead of overwriting a newer save. An item that fails gets status: "error" with VALIDATION_ERROR, INVALID_IMAGE_URL or REVISION_CONFLICT and details naming each path. It is left untouched, and the other items still sync. An editor still holding the previous revision gets 409 REVISION_CONFLICT on its next PATCH with base_revision.

You usually don't call this endpoint yourself — the plugin does it. But it's documented in case you're integrating Figma another way (e.g., a custom Figma extension).

When this is useful

  • A designer needs to make pixel-perfect tweaks beyond what the JSON tree exposes.
  • You want a human-in-the-loop step before publishing an ad.
  • You're A/B-testing manual Figma variants against the AI baseline.

Pitfalls

  • Sync only accepts ads the editor supports (editor_supported: true).
  • figma_render_base64 is the raw PNG bytes, base64-encoded, without the data:image/png;base64, prefix.
  • The full tree is required when you change it, as a schema-v2 document. If you only want to update the rendered PNG without overwriting the tree, omit current_json_tree.
  • Use fonts we ship (Supported fonts) for the render to match Figma. A Figma-only font such as a local or paid font still syncs: our render draws it in Inter at the closest style, and the tree keeps the original font for Figma.
  • Ads created before schema v2 are returned in the retired tree format, which sync does not accept. Generate them again to edit them.
  • Up to 10 ads per call. Larger batches must be split.