# Catalog

Every operation of the ScrollStash API, generated from the API contract: 85 operations.
Each operation has one REST endpoint.

REST paths are relative to `https://api.scrollstash.com`. Paths with `{ws}` act on a workspace: your API key's own workspace id (`GET /v1/me` → `data.workspace_id`).
Paginated operations take `limit` (max 100) and `cursor`; the next cursor is `meta.cursor`.

## system

| REST | Scope | What it does |
| --- | --- | --- |
| `GET /v1/system/freshness` | `system:read` | Corpus size, last scrape time and per-category coverage. |

## brands

| REST | Scope | What it does |
| --- | --- | --- |
| `GET /v1/brands/{brandId}` | `brands:read` | One brand (by id or slug) with its current ad statistics. |
| `GET /v1/brands` (paginated) | `brands:read` | Public brands, filterable by category, country and name, sorted by activity. |
| `GET /v1/brands/sitemap` (paginated) | `brands:read` | Indexable brand pages for sitemap generation (slug, url, last update), optionally within a category at any level. |
| `GET /v1/brands/{brandId}/velocity` | `brands:read` | Weekly ad counts for one brand: every ISO week with an observed day. active_ads is the latest observed day's value; new_ads and retired_ads sum reliable days only (reliable_days, 0–7, says how many) — shade weeks with reliable_days 0. |
| `GET /v1/brands/{brandId}/breakdown` | `brands:read` | Active ads by creative format for one brand. |
| `GET /v1/brands/{brandId}/similar` | `brands:read` | Sites Similarweb rates as similar to this brand, by affinity, linked to tracked brands. |
| `GET /v1/brands/{brandId}/trajectory` | `brands:read` | Similarweb global / category rank and monthly visits over time, oldest first. |
| `GET /v1/brands/{brandId}/demographics` | `brands:read` | Similarweb audience gender and age shares for the brand's domain. |
| `GET /v1/brands/{brandId}/tech-stack` | `brands:read` | Platform tools detected on the brand's site, by tool category then name. |
| `POST /v1/workspaces/{ws}/tracked-brands` | `tracking:write` | Track a brand in a workspace; the scraper hand-off happens asynchronously. |
| `GET /v1/workspaces/{ws}/tracked-brands` (paginated) | `brands:read` | The workspace's tracked brands, newest first, each with its current public summary. |
| `GET /v1/workspaces/{ws}/tracked-brands/freshness` | `brands:read` | Freshness SLO status of the workspace's tracked brands, most overdue first: the last reliable scrape session, when the next is due, and whether the cadence's SLO is breached. |
| `DELETE /v1/workspaces/{ws}/tracked-brands/{brandId}` | `tracking:write` | Stop tracking a brand in a workspace; answers removed:false when it was not tracked. |
| `GET /v1/brands/{brandId}/facebook-pages` | `brands:read` | Facebook pages linked to the brand with their servable role (quarantined links dropped, unservable claims as UNVERIFIED), followers and ad counts, most ads first. |
| `GET /v1/brands/{brandId}/leakage` | `brands:read` | How many of the brand's active ads run on pages it does not own: totals over every page, and the ten largest third-party pages. |
| `GET /v1/brands/{brandId}/copy-reuse` | `brands:read` | Ad body texts the brand runs that pages outside its linked set also run — at most twelve, by foreign ads. |
| `GET /v1/brands/{brandId}/partnerships` | `brands:read` | Creators the brand ran branded content with in the last 365 days, both directions, merged per creator, most ads first. |
| `GET /v1/brands/{brandId}/creative-library` | `brands:read` | A sample of the brand's active ads with stored media (half the longest-running, half the newest; at most `sample`, answered as sample_cap), plus format, CTA and longevity totals over every active ad. |
| `GET /v1/brand-thumbs` | `brands:read` | Up to four of each brand's newest live creatives with a stored image or poster. Every brand asked is answered; an empty list means none is stored. |
| `GET /v1/brands/{brandId}/daily-activity` | `brands:read` | Daily active, new and retired ads for one brand, oldest first. Unreliable days carry reliable:false (draw a gap); days never observed are absent, not zero. |
| `GET /v1/brands/{brandId}/split-tests` | `brands:read` | The testing tools installed on the brand's site and every test a collector read on their accounts: type, state and its basis, arms with configured allocations, the pages each acts on with the brand's active ads there, and the category's test-type profile. Configured and observed: it never says which arm did better. No price or shipping amount is served — a price test says it is one. |
| `GET /v1/brands/{brandId}/store` | `brands:read` | The brand's public Shopify catalog as the collector read it: the latest scan's contract (only an exhausted enumeration is the whole feed), products with variants, listed prices and the available flag as published, and twelve weeks of observed changes. A brand never scanned answers status not_collected, never an empty catalog. |
| `GET /v1/brand-network` | `brands:read` | The shared-page network of one brand (it and its direct neighbours) or of one category (its public brands): brands, brand-to-brand links with their confidence and shared page count, and how many brands in a category have no link. Ownership claims are not part of it. |

## categories

| REST | Scope | What it does |
| --- | --- | --- |
| `GET /v1/categories/{categoryId}` | `categories:read` | One category (any level) with its parent and current totals. |
| `GET /v1/categories` (paginated) | `categories:read` | Top-level categories, or the children of one parent; with `slug`, the categories of any level carrying that slug (the site resolves `/categories/…` paths with it). |
| `GET /v1/categories/sitemap` (paginated) | `categories:read` | Public category pages (every level) for sitemap generation. |
| `GET /v1/categories/{categoryId}/stats` | `categories:read` | Aggregates for one category: brands, landing URLs, Similarweb visits, top page types and top tools. |

## rankings

| REST | Scope | What it does |
| --- | --- | --- |
| `GET /v1/rankings` (paginated) | `rankings:read` | Brand leaderboard for the brands whose top traffic country is `country`, filterable by category. |
| `GET /v1/rankings/risers` (paginated) | `rankings:read` | Brands whose active ads grew most between a reliable day in the last week and one 7–28 days earlier. |
| `GET /v1/rankings/new-advertisers` (paginated) | `rankings:read` | Brands tracked before the window whose first attributed ad (Meta's start date) started in the last `days` days, most active first. Read model rewritten nightly. |

## ads

| REST | Scope | What it does |
| --- | --- | --- |
| `GET /v1/ads` (paginated) | `ads:read` | Search ads by copy, format, status and country. Without `brand_id` the scan is bounded to ads started in the last 90 days. |
| `GET /v1/ads/{adId}` | `ads:read` | One ad with its full copy, archived media and observation window. |
| `GET /v1/brands/{brandId}/ads` (paginated) | `ads:read` | A brand's attributed ads (brand_ad_map), newest start first. |

## creatives

| REST | Scope | What it does |
| --- | --- | --- |
| `GET /v1/creatives` (paginated) | `creatives:read` | Archived creative assets for an ad, or for a brand's attributed ads. |
| `GET /v1/creatives/{creativeId}` | `creatives:read` | One archived creative asset. |
| `GET /v1/ads/{adId}/creatives` | `creatives:read` | Every archived creative asset of one ad. |
| `GET /v1/creative-records` (paginated) | `creatives:read` | Search creative records (one per stored file, not per ad) by brand, analysis depth, media kind, any of the values of each closed field, words said or written on screen, and numeric ranges; most live ads first. Counts are ads carrying a file, never spend. |
| `GET /v1/creative-records/{creativeKey}` | `creatives:read` | One creative record by its key or a prefix of at least ten characters: media facts with frame times, the transcript with corrections, the classification, and the corpus join (ads carrying it, live ads, first start, landing page, creator-run). An ambiguous prefix is a conflict. |
| `GET /v1/creative-coverage` | `creatives:read` | How far the pipeline has taken a brand's creative files (or every public brand's), against its measured file count, and each closed field's values over the described files — the denominators every creative share is printed with. |

## pages

| REST | Scope | What it does |
| --- | --- | --- |
| `GET /v1/pages` (paginated) | `pages:read` | Landing pages of public US ecommerce brands on hosts they own — newest first, or most-seen first with `sort=popular`; filter by page type, category or a URL substring. With `brand_id` or `category_id`, every live page of that public brand, or of the brands in that category at any level — any country. |
| `GET /v1/pages/{pageId}` | `pages:read` | One landing page with its scraped metadata and classification. |
| `GET /v1/page-details` | `pages:read` | Up to 100 landing pages with their scraped metadata and classification, by id. Unknown or non-public ids are left out. |

## operators

| REST | Scope | What it does |
| --- | --- | --- |
| `GET /v1/operators` (paginated) | `operators:read` | Operators: companies owning several domains or pages. |
| `GET /v1/operators/{operatorId}` | `operators:read` | One operator with the public brands it owns. |
| `GET /v1/hubs` (paginated) | `operators:read` | Facebook pages running paid ads for two or more brands — affiliate hubs, commerce publishers, creator pages — with each brand roster and live creative counts, biggest roster first. |
| `GET /v1/brands/{brandId}/operators` | `operators:read` | The operators that own this brand's domain, each with its other public brands. |

## tools

| REST | Scope | What it does |
| --- | --- | --- |
| `GET /v1/tools` (paginated) | `tools:read` | Platform tools detected on public brands' sites, most adopted first. |
| `GET /v1/tools/{toolSlug}` | `tools:read` | One tool (by slug or uuid) with adoption by category, common combinations and a traffic split. |
| `GET /v1/tools/{toolSlug}/brands` (paginated) | `tools:read` | Public brands using a tool, by Similarweb monthly visits; `country` keeps the brands whose top traffic country it is. |
| `GET /v1/split-test-census` | `tools:read` | Every A/B-testing tool: public brands with it installed, how many of those a collector has read, how many run at least one experiment, and whether we read the tool at all (null counts mean not read, never zero). Personalizations and rollouts are not counted as tests. |

## audience

| REST | Scope | What it does |
| --- | --- | --- |
| `GET /v1/audience/{segment}` (paginated) | `audience:read` | Public brands whose Similarweb audience falls in a segment, by monthly visits, with their demographics. |

## me

| REST | Scope | What it does |
| --- | --- | --- |
| `GET /v1/me` | `me:read` | The caller's workspace, role, plan and granted scopes. |

## apiKeys

| REST | Scope | What it does |
| --- | --- | --- |
| `POST /v1/workspaces/{ws}/api-keys` | `apiKeys:write` | Create an API key for a workspace. The plaintext key is in this response only; scopes must be a subset of the caller's. |
| `GET /v1/workspaces/{ws}/api-keys` (paginated) | `apiKeys:read` | The workspace's API keys, newest first, revoked ones included; never the key itself. |
| `POST /v1/workspaces/{ws}/api-keys/{keyId}/rotate` | `apiKeys:write` | Revoke a key and issue its replacement with the same name and scopes. The new plaintext is in this response only. |
| `DELETE /v1/workspaces/{ws}/api-keys/{keyId}` | `apiKeys:write` | Revoke a key. Requests with it fail with 401 from the next request on. |
| `POST /v1/workspaces/{ws}/api-keys/revoke-unused` | `apiKeys:write` | Revoke every active key not used in the given number of days (never-used keys count from creation). |

## radar

| REST | Scope | What it does |
| --- | --- | --- |
| `GET /v1/workspaces/{ws}/radar` (paginated) | `tracking:read` | Observed ad changes across the workspace's tracked brands in the last `days` days, newest first: ads started, ads the scraper confirmed inactive, and ads whose destination link changed. |
| `GET /v1/workspaces/{ws}/radar/signals` (paginated) | `tracking:read` | Grouped radar signals for the workspace's tracked brands in the last `days` days, newest first: ads started, confirmed stops and link changes per brand and day, plus activity changes (active-ad ramps and drops, launch bursts, sales, new landing pages, new linked pages, new whitelisting pairs, format-mix shifts, long-running ads refreshed). Each carries evidence links. |

## alerts

| REST | Scope | What it does |
| --- | --- | --- |
| `GET /v1/workspaces/{ws}/alerts` (paginated) | `alerts:read` | The workspace's alert rules, newest first. |
| `POST /v1/workspaces/{ws}/alerts` | `alerts:write` | Create an alert rule on brands the workspace tracks: a trigger (new ads, confirmed stopped ads, page changes, new partners or activity coverage), a delivery cadence, and its channels (email to the creator, optional https webhook or Slack incoming webhook). The first webhook rule answers the workspace's webhook signing secret once. |
| `PATCH /v1/workspaces/{ws}/alerts/{alertId}` | `alerts:write` | Change an alert rule's name, trigger, brands, cadence, channels or whether it is enabled; webhook_url null removes the webhook. |
| `DELETE /v1/workspaces/{ws}/alerts/{alertId}` | `alerts:write` | Delete an alert rule and its delivery history; answers removed:false when it did not exist. |
| `GET /v1/workspaces/{ws}/alert-deliveries` (paginated) | `alerts:read` | The workspace's alert deliveries, newest first, optionally for one rule. |
| `POST /v1/workspaces/{ws}/alerts/{alertId}/test` | `alerts:write` | Send a sample delivery for an alert rule on each of its channels now and answer what happened. Webhooks are POSTed JSON signed with the workspace secret: header x-scrollstash-signature is v1=hex(HMAC-SHA256(secret, `${x-scrollstash-timestamp}.${raw body}`)). |
| `POST /v1/workspaces/{ws}/alerts/webhook-secret` | `alerts:write` | Create or rotate the workspace's alert webhook signing secret and answer it once; it is never returned again and the previous secret stops signing immediately. |

## briefs

| REST | Scope | What it does |
| --- | --- | --- |
| `GET /v1/workspaces/{ws}/briefs` (paginated) | `briefs:read` | The workspace's weekly briefs, newest period first: each period's radar change counts across its tracked brands. |
| `GET /v1/workspaces/{ws}/briefs/{briefId}` | `briefs:read` | One weekly brief: change counts per tracked brand for the period and up to ten notable radar items. |
| `PATCH /v1/workspaces/{ws}/briefs/{briefId}` | `briefs:write` | Record whether a brief was useful, already known or wrong; null clears it. |

## reports

| REST | Scope | What it does |
| --- | --- | --- |
| `POST /v1/workspaces/{ws}/reports` | `reports:write` | Queue a CSV export of the radar (ads started, ads stopped, link changes) for tracked brands over up to 31 days. Returns the queued report at once; an `idempotency_key` reused in the workspace returns the report it created. |
| `GET /v1/workspaces/{ws}/reports` (paginated) | `reports:read` | The workspace's reports, newest first, each with its status and download URL once ready. |
| `GET /v1/workspaces/{ws}/reports/{reportId}` | `reports:read` | One report with its status; `download_url` is set once it is ready. |

## collections

| REST | Scope | What it does |
| --- | --- | --- |
| `GET /v1/workspaces/{ws}/collections` (paginated) | `collections:read` | The workspace's collections with their items, most recently changed first. Private boards appear only to their creator and editors. |
| `POST /v1/workspaces/{ws}/collections` | `collections:write` | Create a collection, optionally inside a folder and with a first item. |
| `PATCH /v1/workspaces/{ws}/collections/{collectionId}` | `collections:write` | Rename, describe, move, or change who can see and edit a collection. |
| `DELETE /v1/workspaces/{ws}/collections/{collectionId}` | `collections:write` | Delete a collection and every collection inside it. |
| `POST /v1/workspaces/{ws}/collections/{collectionId}/items` | `collections:write` | Add an item to a collection; adding one already there changes nothing. |
| `DELETE /v1/workspaces/{ws}/collections/{collectionId}/items/{itemType}/{itemKey}` | `collections:write` | Remove an item from a collection; answers removed:false when it was not there. |
| `GET /v1/workspaces/{ws}/saved` (paginated) | `collections:read` | The workspace's saved items, newest first, optionally of one type. |
| `POST /v1/workspaces/{ws}/saved` | `collections:write` | Save an item; saving one already saved changes nothing. |
| `DELETE /v1/workspaces/{ws}/saved/{itemType}/{itemKey}` | `collections:write` | Unsave an item; answers removed:false when it was not saved. |

## notes

| REST | Scope | What it does |
| --- | --- | --- |
| `GET /v1/workspaces/{ws}/notes` (paginated) | `notes:read` | The workspace's notes, most recently edited first, optionally on one subject type or one subject. |
| `POST /v1/workspaces/{ws}/notes` | `notes:write` | Write the caller's note on a subject: creates it, or replaces the caller's existing note on that subject. |
| `DELETE /v1/workspaces/{ws}/notes/{noteId}` | `notes:write` | Delete a note; answers removed:false when it did not exist. |
