Static Ads Lab
Concepts

Swipe file

A searchable library of reference images. Upload competitor ads and inspiration, then find them by natural-language search.

A swipe file is a lightweight, org-scoped library of static reference images — competitor ads, inspiration, anything you want to point at later. Each upload is embedded and auto-classified so you can retrieve it with filtered vector search. Unlike design templates, swipe files are just the image plus metadata — no JSON tree, no reverse-engineering pipeline.

Mental model

Use a swipe file when you want to stash a reference image and find it fast, without turning it into a structured, editable end-product. On upload, each image is:

  1. Stored (compressed, with a thumbnail).
  2. Embedded with a multimodal model into a shared text/image vector space.
  3. Auto-classified into one industry and one ad_format from our standard taxonomies.

You then retrieve images with filtered vector search: a text query is embedded and cosine-matched against the image embeddings, optionally narrowed by industry, ad_format, and aspect_ratio.

Your library is the union of your own uploads and the data sources your organization is subscribed to — curated collections managed by Static Ads Lab that you can toggle on or off at any time.

Swipe files are free — there is no per-image charge.

ID prefix: sf_ (swipe files), sfs_ (data sources).

Fields

FieldTypeDescription
idstringsf_…
image_urlstringURL of the stored image
thumbnail_urlstring | nullURL of a smaller thumbnail
width / heightnumber | nullPixel dimensions
aspect_ratiostring | nulle.g. "1:1", "4:5", "9:16"
industrystring | nullAuto-classified Shopify L1 category
ad_formatstring | nullAuto-classified creative format
sourceenumupload | import | studio_concept | sal_managed
is_managedbooleanTrue for designs from a managed data source (read-only)
statusenumprocessing | ready | failed
errorstring | nullFailure reason when status is failed
created_at / updated_atstringISO timestamps

Lifecycle

stateDiagram-v2
  [*] --> processing: POST /v1/swipe-files (multipart upload)
  processing --> ready: embed + classify succeed
  processing --> failed: embedding failed
  ready --> [*]
  failed --> [*]

Ingest is asynchronous. The upload returns immediately with status: "processing"; the item becomes ready once its embedding and classification finish. A swipe file only appears in search results once it is ready. If classification degrades, the item still becomes ready with industry/ad_format left null — only an embedding failure marks it failed.

Endpoints

MethodPathPurpose
POST/v1/swipe-filesUpload a reference image (multipart)
GET/v1/swipe-filesList swipe files with filters
GET/v1/swipe-files/:idGet a swipe file
DELETE/v1/swipe-files/:idArchive (soft-delete) a swipe file
POST/v1/swipe-files/:id/restoreRestore an archived swipe file
POST/v1/swipe-files/searchFiltered vector search
GET/v1/swipe-files/data-sourcesList data sources with subscription state
PUT/v1/swipe-files/data-sources/:id/subscriptionEnable or disable a data source

Data sources

Beyond your own uploads, your swipe file can pull from data sources: shared, Static Ads Lab-curated collections of reference ads. New organizations are automatically subscribed to the default collection, so your library is stocked from day one. Subscriptions are per-organization and idempotent to toggle.

  • Designs from a subscribed source appear in your list and search results alongside your own uploads, marked with is_managed: true and source: "sal_managed".
  • Managed designs are read-only: archiving, restoring, or deleting them returns SWIPE_FILE_READ_ONLY. To hide them, disable the source.
  • Data sources are free — the curated content adds no charges to your account.

List available data sources

const response = await fetch("https://api.staticadslab.com/v1/swipe-files/data-sources", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const { data } = await response.json();
for (const source of data) {
  console.log(source.id, source.name, source.subscribed, source.item_count);
}
// sfs_all_sal_ads "All Static Ads Lab Ads" true 240

Enable or disable a data source

await fetch(
  "https://api.staticadslab.com/v1/swipe-files/data-sources/sfs_all_sal_ads/subscription",
  {
    method: "PUT",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ active: false }), // true re-enables
  },
);

Scope list/search to specific sources

Both GET /v1/swipe-files and POST /v1/swipe-files/search accept data_source_ids and include_own:

const response = await fetch("https://api.staticadslab.com/v1/swipe-files/search", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    query: "bold typography sale announcement",
    data_source_ids: ["sfs_all_sal_ads"], // only this source's designs
    include_own: false,              // exclude my own uploads
  }),
});

Omit data_source_ids to search everything you are subscribed to; pass data_source_ids: [] with include_own: true to search only your own uploads.

Common patterns

Upload a reference image

Upload raw bytes directly with multipart/form-data — no need to pre-host the image. search and read require the swipe_files:read scope; uploads require swipe_files:write.

const form = new FormData();
form.append("file", fileBlob, "competitor-ad.png"); // File or Blob

const response = await fetch("https://api.staticadslab.com/v1/swipe-files", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    // Do NOT set Content-Type — the runtime adds the multipart boundary.
  },
  body: form,
});
const { data } = await response.json();
console.log(data.id, data.status); // sf_..., "processing"

Then poll until ready:

async function waitForSwipeFile(id) {
  while (true) {
    const r = await fetch(
      `https://api.staticadslab.com/v1/swipe-files/${id}`,
      { headers: { "X-API-Key": "YOUR_API_KEY" } },
    );
    const { data } = await r.json();
    if (data.status === "ready") return data;
    if (data.status === "failed") throw new Error(data.error ?? "Ingest failed");
    await new Promise((res) => setTimeout(res, 2000));
  }
}

Search by description

const response = await fetch("https://api.staticadslab.com/v1/swipe-files/search", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    query: "minimalist beige flatlay with soft shadows",
    ad_format: ["Sale", "Offer"], // optional filters; string or array (matches any)
    aspect_ratio: "1:1",
    limit: 20,
  }),
});
const { data } = await response.json();
for (const hit of data) {
  console.log(hit.id, hit.similarity, hit.image_url);
}

Every metadata filter (industry, ad_format, aspect_ratio) accepts a single string or an array of strings. Multiple values on one dimension match any of them; different dimensions combine with AND.

List and filter without a query

// Repeat a query param to match any of multiple values on that dimension.
const params = new URLSearchParams({ status: "ready" });
params.append("industry", "Apparel & Accessories");
params.append("industry", "Health & Beauty");
const response = await fetch(
  `https://api.staticadslab.com/v1/swipe-files?${params}`,
  { headers: { "X-API-Key": "YOUR_API_KEY" } },
);
const { data, has_more } = await response.json();

Pitfalls

  • Ingest is async. A newly uploaded swipe file is processing and will not appear in /search results until it is ready. Poll GET /v1/swipe-files/:id or list with status=processing to track it.
  • search is a POST but read-intent — it requires the swipe_files:read scope, not write.
  • Supported upload formats are PNG, JPEG, and WebP. WebP is transcoded to PNG before embedding.
  • Your library is org-scoped plus any subscribed data sources. If unexpected designs appear (or disappear), check GET /v1/swipe-files/data-sources — a subscription toggle changes list and search results immediately.
  • Managed designs (is_managed: true) cannot be archived or restored — DELETE returns SWIPE_FILE_READ_ONLY. Disable the data source instead.

Prompt for your agent

Read https://www.staticadslab.com/docs/resources/swipe-file.mdx and write a function that uploads a reference image to the swipe file via multipart POST /v1/swipe-files, polls until status is ready, then runs POST /v1/swipe-files/search with a text query and returns the top matches with their similarity scores.