> ## Documentation Index
> Fetch the complete documentation index at: https://www.octogen.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Coverage API - URL Lists

> URL Lists provide detailed access to Octogen catalog data via BigQuery

A **URL list** is a set of product URLs you own and edit through the API. Each
list automatically gets its own BigQuery listing, refreshed daily with the
full product records for every URL Octogen covers — so instead of looking up
URLs one at a time or subscribing to whole catalogs and joining on your side,
you upload the URLs you care about and read the answer in BigQuery.

## How it works

<Steps>
  <Step title="Create a list">
    One API call. A BigQuery listing is provisioned automatically — typically
    within a minute — and shared with the BigQuery Readers you've already
    registered with Octogen.
  </Step>

  <Step title="Add and remove URLs any time">
    Batch calls, up to 1,000 URLs each. Re-sending URLs that are already there
    is a harmless no-op, so you can sync your side blindly.
  </Step>

  <Step title="Read fresh results in BigQuery every day">
    Each covered URL becomes full product rows — title, brand, price, images,
    variants, and the rest — in exactly the schema your existing catalog
    listings use. A per-URL coverage view says which of your URLs matched and
    which didn't.
  </Step>
</Steps>

## The API at a glance

Same base URL and API key as the rest of the platform API
(`https://api.octogen.ai/v1`). See the
[Coverage API reference](/docs/api-reference/create-url-list) for complete schemas.

| Endpoint                                             | What it does                                                                                |
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `POST /coverage/url-lists`                           | Create a list                                                                               |
| `GET /coverage/url-lists`                            | List your lists                                                                             |
| `GET /coverage/url-lists/{urlListId}`                | Get one list — status, URL count, where its BigQuery data lives, when it was last refreshed |
| `POST /coverage/url-lists/{urlListId}/urls`          | Add URLs (batch)                                                                            |
| `POST /coverage/url-lists/{urlListId}/urls/remove`   | Remove URLs (batch)                                                                         |
| `POST /coverage/url-lists/{urlListId}/urls/contains` | Check whether specific URLs are in the list                                                 |
| `GET /coverage/url-lists/{urlListId}/urls`           | Page through the list's URLs                                                                |
| `DELETE /coverage/url-lists/{urlListId}`             | Delete the list and its BigQuery data                                                       |

## Create a list and add URLs

<CodeGroup>
  ```bash curl theme={null}
  curl -sS https://api.octogen.ai/v1/coverage/url-lists \
    -H "Authorization: Bearer $OCTO_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"name": "q3-campaign"}'

  curl -sS https://api.octogen.ai/v1/coverage/url-lists/cul_01KZAC9QSY5RWSTZ63FGBS50F2/urls \
    -H "Authorization: Bearer $OCTO_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"urls": [
      "https://shop.example/products/dress?utm_source=newsletter",
      "https://shop.example/collections/summer/products/linen-shirt"
    ]}'
  ```

  ```python python theme={null}
  import asyncio
  from octogen_ai_sdk import OctogenClient

  async def main() -> None:
      async with OctogenClient() as client:
          url_list = await client.create_coverage_url_list(name="q3-campaign")
          result = await client.add_coverage_url_list_urls(
              url_list.url_list_id,
              urls=[
                  "https://shop.example/products/dress?utm_source=newsletter",
                  "https://shop.example/collections/summer/products/linen-shirt",
              ],
          )
          print(result.url_count, "URLs;", len(result.rejected), "rejected")

  asyncio.run(main())
  ```

  ```typescript typescript theme={null}
  import { OctogenClient } from "@octogen-ai/sdk";

  const client = new OctogenClient();
  const urlList = await client.createCoverageUrlList("q3-campaign");
  const result = await client.addCoverageUrlListUrls(urlList.urlListId, [
    "https://shop.example/products/dress?utm_source=newsletter",
    "https://shop.example/collections/summer/products/linen-shirt",
  ]);
  console.log(result.urlCount, "URLs;", result.rejected.length, "rejected");
  ```
</CodeGroup>

* **Adds are idempotent** and report exactly what happened to each URL:
  `accepted` echoes each stored URL alongside its `normalizedUrl`, and
  unparseable entries land in `rejected` with code `invalid_url` — without
  failing the batch.
* **Batches are capped at 1,000 URLs per request**, so loop over larger
  files.
* **The new list starts in `provisioning`** and typically becomes `active` —
  with its `bigQuery` details populated — within a minute. You never have to
  wait: URL mutations work in every live state.

## Check membership

`POST .../urls/contains` tells you whether URLs are **in your list** (added by
you), applying the same normalization as adds. Coverage results — whether
Octogen's catalogs match those URLs — live in BigQuery, below.

```bash curl theme={null}
curl -sS https://api.octogen.ai/v1/coverage/url-lists/cul_01KZAC9QSY5RWSTZ63FGBS50F2/urls/contains \
  -H "Authorization: Bearer $OCTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://shop.example/products/dress"]}'
```

## Query your coverage in BigQuery

Once the list is `active`, its `bigQuery` block names an Analytics Hub listing
in the `catalogs_prod` exchange:

```json theme={null}
"bigQuery": {
  "exchangeId": "catalogs_prod",
  "listingId": "coverage_cul_01KZAC9QSY5RWSTZ63FGBS50F2_v1",
  "sharedDatasetId": "coverage_share_cul_01KZAC9QSY5RWSTZ63FGBS50F2_v1",
  "viewId": "products_current_v1",
  "lastExportedAt": "2026-08-06T06:31:12Z",
  "lastRowCount": 1128,
  "readerCount": 1
}
```

* **Access is granted automatically** to your organization's registered
  BigQuery Readers — the same Readers used for catalog listings — so
  subscribing works exactly like the
  [BigQuery Subscribe guide](/docs/guides/bigquery-subscribe) describes.
* **The view holds the currently matched products** for your list, with the
  same columns as catalog exports (`uuid`, `catalog_key`, `product_url`,
  `title`, `brand`, prices, `variants`, `enrichment`, and more). Pipelines
  you've already built just work.
* **Each daily export replaces the previous snapshot**; `lastExportedAt` and
  `lastRowCount` on the list object tell you what the view currently
  reflects.
* **A URL with no matching row simply isn't covered yet** — and the
  `url_coverage_v1` view, below, tells you exactly which URLs those are.

After subscribing, query the linked dataset:

```sql theme={null}
SELECT catalog_key, product_url, title, current_price, in_stock
FROM `my-gcp-project.octogen_coverage_q3.products_current_v1`
ORDER BY catalog_key, title
```

## See exactly which URLs are covered

The same dataset carries a second view, **`url_coverage_v1`** — one row per
URL in your list, saying whether it matched:

| Column           | Meaning                                           |
| ---------------- | ------------------------------------------------- |
| `url`            | The URL as you submitted it                       |
| `normalized_url` | Octogen's normalized form of it                   |
| `covered`        | `TRUE` if the export matched at least one product |
| `exported_at`    | The snapshot timestamp                            |

The uncovered portion of your list is one query:

```sql theme={null}
SELECT url
FROM `my-gcp-project.octogen_coverage_q3.url_coverage_v1`
WHERE NOT covered
```

* **No action needed** — the view appears automatically in your linked
  dataset, including datasets you subscribed before it existed.
* **Same refresh as the products view**: both are written by the same daily
  export, so they always describe the same snapshot. URLs added since the
  last export show up on the next one.
* **`covered = TRUE` means the products view has at least one row for that
  URL.** Don't infer coverage by comparing row counts across the two views —
  one URL can match several products and several URLs can match one product,
  so the counts aren't ordered. This view is the per-URL ground truth.

## Behavior worth knowing

* **You don't need to clean your URLs.** Octogen normalizes them — tracking
  parameters, `http/https`, and host-case differences won't cause misses or
  duplicates, and meaningful query parameters are preserved.
* **Matching is page-level.** URLs that differ only in query parameters
  generally resolve to the same product page and are matched as one; common
  variants such as Shopify `/collections/<name>/products/<handle>` paths
  match the same product's `/products/<handle>` URL.
* **Results refresh once a day.** `lastExportedAt` on the list tells you
  when. Between refreshes, edits to the list don't change BigQuery.
* **Enumerate any time.** `GET .../urls` pages through the list in insertion
  order, so you can reconcile against your source of truth.
* **Delete means delete.** Access is cut within minutes and the data is
  removed. There's no undelete — recreate the list if you change your mind.
  The name and list slot free up immediately.
* **Limits:** 5 lists per organization, 100,000 URLs per list, 1,000 URLs
  per request.

## Next steps

* [Coverage API reference](/docs/api-reference/create-url-list)
* [BigQuery Subscribe guide](/docs/guides/bigquery-subscribe)
* [Error handling guide](/docs/guides/error-handling)
