---
title: "Stored read: List ads"
method: GET
path: "/observe/ads"
tags: ["Ads"]
---

# Stored read: List ads

`GET /observe/ads`

## Request

- Base URL: `https://api.adcrunch.dev`
- URL: `https://api.adcrunch.dev/observe/ads`
- Auth: HTTP bearer

**Available on:** [![Meta](/providers/meta.svg)](https://docs.adcrunch.dev/connect/providers) [![TikTok](/providers/tiktok.svg)](https://docs.adcrunch.dev/connect/providers) [![Google Ads](/providers/gads.svg)](https://docs.adcrunch.dev/connect/providers) [![OpenAI](/providers/openai.svg)](https://docs.adcrunch.dev/connect/providers)

Lists the ads of every ad account your organization connects, across the providers, newest first.

## One concept, two words

An ad is an `ad` at Meta and TikTok. At Google Ads it is an **`ad_group_ad`** — the link resource that joins an ad to an ad group, whose id is the composite `{adGroupId}~{adId}`. AdCrunch stores that link as the leaf and does not model a separate shared ad, so a Google row answers the composite id. Every row keeps its own provider’s word in `type`.

## Narrowing

Two parents, and they are not the same question. `adGroupId` reads the ads of one ad group. `campaignId` reads **every** ad of a campaign, whatever ad group holds it — a grandparent, which the ad group filter cannot express. Send both to narrow twice.

## What is not here

Performance Max campaigns have **no ads**. Google’s automation assembles placements from an asset group, so nothing below a Performance Max campaign appears here.

## Query parameters

- `ids` string: Ids to filter on, separated by commas. An id is the bare id its provider gives.
- `cursor` string: The `nextCursor` of the previous page. Omit it to get the first page. Send it with no change, and with the same filters as the request that answered it: a cursor from a different query gets a 400 `invalid_cursor`. Do not build or change a cursor.
- `limit` integer: The greatest number of rows on the page, from 1 to 500. The default is 100. A greater value gets a 400, with `error` of `invalid_request`.
- `advertiserId` string: Read one ad account from what AdCrunch holds, prefixed `acc_`. One your organization does not own answers an empty list rather than a failure. To ask the provider instead, put the advertiser in the path.
- `provider` union: Read one provider. Omit it to read every provider your organization connects.
  - 'meta'
  - 'tiktok'
  - 'snapchat'
  - 'gads'
  - 'dv360'
  - 'x'
  - 'openai'
- `status` 'ACTIVE' | 'PAUSED' | 'DELETED' | 'ARCHIVED': Read one status, normalized across the providers. TikTok `ENABLE` and `DISABLE` are read here as `ACTIVE` and `PAUSED`.
- `campaignId` string: Read only what this campaign holds. Send the bare id its provider gives, which is the `campaignId` every row answers.
- `adGroupId` string: Read only what this ad group holds. Send the bare id its provider gives, which is the `adGroupId` every row answers.

## Response `200`

The matching ads, and how many there are in all. Each row keeps its provider’s own `type`: `ad`, or `ad_group_ad` at Google Ads.

- object
  - `data` object[], required: The matching rows. From the store, the row AdCrunch stored last comes first; from the provider, the provider’s own order. An empty array means nothing matched, which is a normal answer and not a failure.
    - `advertiserId` string, required: The advertiser that owns it, prefixed `acc_`.
    - `createdAt` number, nullable, required: When AdCrunch first stored this row. **Null when the row was read live**: AdCrunch holds no copy of it. `createdAt`, `updatedAt` and `deletedAt` are AdCrunch’s own times, so all three are null together on a live row.
    - `createdTime` number, nullable, required: When the provider created the entity. This is the provider’s own time, so a live row carries it. Null where the provider reports none.
    - `currency` string, nullable, required: The account currency, ISO 4217. Every row of one advertiser carries the same one. Null when AdCrunch does not know it yet.
    - `deletedAt` number, nullable, required: When AdCrunch marked the row deleted. A listing never carries a deleted row, so this is null.
    - `id` string, required: The id the provider gives, with no prefix. It is unique for one provider and one type, and it may legitimately recur across two types or two providers.
    - `name` string, required: The name at the provider.
    - `path` string, required: The ancestors and this entity, ids joined by `/`, oldest first. This is what makes a subtree one string comparison.
    - `provider` string, required: The ad platform: `meta`, `tiktok` or `gads`. Those three are the providers whose entities AdCrunch reads, and https://docs.adcrunch.dev/connect/providers says how deep each one goes.
    - `status` 'ACTIVE' | 'PAUSED' | 'DELETED' | 'ARCHIVED', required: The status, normalized across the providers. TikTok `ENABLE` and `DISABLE` read here as `ACTIVE` and `PAUSED`.
    - `updatedAt` number, nullable, required: When AdCrunch last rewrote this row. Null if it never changed.
    - `updatedTime` number, nullable, required: When the provider last edited the entity. Null where the provider reports none.
    - `adGroupId` string, nullable, required: The ad group that holds the ad, as the bare id its provider gives. Null only when the stored path of the row names no ad group.
    - `campaignId` string, nullable, required: The campaign it belongs to, as the bare id its provider gives. Null only when the stored path of the row names no campaign.
    - `type` string, required: The type the provider uses, kept as the provider writes it: `ad` at Meta and TikTok, and `ad_group_ad` at Google Ads. The resource names the concept, and the row keeps the provider’s own word, because a Meta `adset` and a Google Ads `ad_group` are not the same object.
  - `nextCursor` string: Send this value as `cursor` to get the next page. It is absent on the last page.

## Other responses

- `400`: The request does not match the schema of this operation: a field is missing or has the wrong type, or the body is not valid JSON. `error` is `invalid_request`, and `issues` names each field. Nothing was changed. Or AdCrunch cannot use the `cursor`: it cannot read it, or the cursor belongs to a different query. Then `error` is `invalid_cursor`. Send the `nextCursor` of the previous page with no change, or omit `cursor` to get the first page.
- `401`: No API key, or one that does not resolve. See the security scheme. `error` is `unauthorized`.
- `402`: The organization has no active subscription. An owner subscribes at `paywallUrl`. `error` is `organization_unentitled`.
- `403`: The caller does not hold `observe:read`. `error` is `forbidden`.

---

[API](https://skmtc.dev/adcrunch/apis/adcrunch-api.md) · [All operations](https://skmtc.dev/adcrunch/apis/adcrunch-api/llms.txt) · [OpenAPI document](https://skmtc.dev/adcrunch/apis/adcrunch-api/revisions/a420ba80914f?raw)
