Static Ads Lab
Concepts

Imports

Import a Shopify product, with its variants and images, from its product URL.

POST /v1/imports brings one product into a brand from its Shopify product page: name, description, every variant and its images. It is the fastest way to get a new product ready to build ads for. ID prefix: brx_. Scopes: products:write to start, products:read to poll. Free.

Start an import

const { data: started } = await sal("/v1/imports", {
  method: "POST",
  idempotencyKey: crypto.randomUUID(),
  body: {
    brand_id: "brand_a1b2c3d4e5f67890",
    url: "https://acme-apparel.com/products/heavyweight-tee",
  },
});
// 202, started.status === "processing"

The store and product are checked first, so a bad URL fails right away with a 400 that says why: INVALID_PRODUCT_URL, NOT_A_PRODUCT_URL or NOT_SHOPIFY_STORE. For a product that isn't on Shopify, create it from its image URLs instead. 503 SHOPIFY_UNAVAILABLE means the store didn't answer; try again in a minute. The import itself runs in the background, usually for one to three minutes.

Poll it

async function waitForImport(id, { intervalMs = 5_000, timeoutMs = 10 * 60_000 } = {}) {
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    const { data } = await sal(`/v1/imports/${id}`);
    if (data.status !== "processing") return data;
    await new Promise((r) => setTimeout(r, intervalMs));
  }
  throw new Error(`Import ${id} still running after ${timeoutMs / 60_000} minutes`);
}

const done = await waitForImport(started.id);
if (done.status === "completed") {
  console.log(done.product.id, done.product.name, done.product.variant_count);
} else {
  console.error(done.failure.code, done.failure.message);
}
FieldNotes
statusprocessing, completed or failed
progress_messageWhat it's doing now
productOnce completed: { id, name, variant_count, image_urls, variants }
failure{ code, message } when failed

The imported product has no audiences. Create one before building ads.