# Look up a Google Ad

> For the complete documentation index, see [llms.txt](https://www.shortsintel.com/llms.txt). Every docs page is also available as markdown: add .md to its URL. To connect, read [agents.md](https://www.shortsintel.com/agents.md).

GET `/v1/ads/google/{creative_id}`

One Google Ad by creative id or Transparency URL, with `variations` and any YouTube id.

- Storage: images archived. Served from our copy within 24 hours, or with `removed_from_library: true` once dropped, same price.
- Errors: no `advertiser_id` for an unseen Ad is 400 `invalid_request`, an unknown Ad 404 `not_found`, both free.
- Analysis: a refused Ad fails the Run and returns no Ad, for free.

[Open in Playground](https://www.shortsintel.com/console/playground?op=getGoogleAd&creative_id=creative_id_example&analyze=true) (no API Key needed)

## Price

3 credits, plus 20 credits with `analyze=true`.

Supports `estimate=true`: the reply is the price in Credits and whether it would be `cached`, with no Run created and nothing charged.

A cached result costs the same as a fresh one; `cached` says which you got.

## Parameters

| Name | In | Type | Required | Default | Description |
| --- | --- | --- | --- | --- | --- |
| `creative_id` | path | `string` | Required | — | The `CR` id, or the URL-encoded Transparency URL. |
| `advertiser_id` | query | `string` | Optional | — | The Ad's Advertiser, as its `AR` id. Required unless the URL names it or we already hold the Ad. |
| `analyze` | query | `string` one of `true`, `false` | Optional | — | `true` to add Ad analysis. |
| `wait` | query | `integer` min 0; max 120 | Optional | `30` | Seconds to hold the response open before handing back a running Run to poll. Default 30, max 120. A POST may send it in the body instead; different values in both places are a 400. |
| `max_credits` | query | `integer` min 0 | Optional | — | Most Credits the call may cost. A higher quote is a 402 `max_credits_exceeded`, no Run. Where `x-price.max_credits` is null, the Run stops at it and settles `partial`. |
| `estimate` | query | `boolean` | Optional | — | `true` returns `{ estimate_credits, cached }` from what is already stored, creating no Run. Nothing is charged. If a source the call would read is unavailable and the cache cannot answer, the estimate adds `unavailable` (the `{ platform, capability }` parts it cannot reach), or is a 503 `source_unavailable` when it can reach none. Only `true` or `false`, else a 400. A POST may send it in the body instead; different values in both places are a 400. |
| `Idempotency-Key` | header | `string` | Optional | — | 1-255 printable ASCII characters. Replaying this key with the same payload returns the original Run (202 while it is still running); a different payload, or a different operation, is a 409 `idempotency_conflict`. |

## Responses

### 200 OK

The Run finished inside the wait budget: `{ run, result }`.

any of 2 shapes

- Option 1 object The envelope every charged operation answers with: `{ run }` while the Run is running (plus `poll_url` on the 202), joined by `result` once it completes or `error` once it fails.
  - `run` object required Every charged operation creates a Run. The envelope is identical whether the result came back inline or was polled.
    - `id` string required
    - `kind` string required What was run, e.g. `video_lookup`.
    - `status` string required one of `running`, `completed`, `partial`, `failed` `partial` keeps the completed items and charges for them, up to what the Balance covers; `failed` charges nothing.
    - `created_at` string required RFC 3339.
    - `completed_at` string required nullable Null while the Run is still running.
    - `charge_credits` integer required What this Run actually cost, in Credits. A stored answer costs the same as a fresh one.
    - `list_price_credits` integer required The full price of the work asked for, in Credits. Equal to `charge_credits` unless the Run charged for only part of it.
    - `cached` boolean required True when a stored result answered without a fresh fetch.
    - `progress` object What a running Research has done so far, refreshed as it works: its `stage` and the counts known by then, such as `analysed` of `shortlisted`. A count is absent until its stage produced it. Absent once the Run settles, and on every other kind.
      - `stage` string required one of `keywords`, `searching`, `reading_creators`, `filtering`, `analysing`, `writing_digest`
      - `keywords` integer value at least 0
      - `creators` integer value at least 0
      - `creators_read` integer value at least 0
      - `searches` integer value at least 0
      - `found` integer value at least 0
      - `judged` integer value at least 0
      - `kept` integer value at least 0
      - `dropped_off_topic` integer value at least 0
      - `dropped_unsure` integer value at least 0
      - `shortlisted` integer value at least 0
      - `analysed` integer value at least 0
      - `updated_at` string required
  - `result` object required
    - `ad` object required
      - `library` string required one of `meta`, `tiktok`, `google`
      - `id` string required
      - `url` string required nullable
      - `advertiser` object required
        - `id` string required nullable
        - `name` string required nullable
      - `is_active` boolean required nullable
      - `first_shown` string required nullable
      - `last_shown` string required nullable
      - `placements` array of string required
      - `creative` object required
        - `title` string required nullable
        - `body` string required nullable
        - `cta` string required nullable
        - `link_url` string required nullable
        - `format` string required nullable
        - `image_urls` array of string required
        - `video_urls` array of string required
        - `thumbnail_urls` array of string required
      - `reach` object required Ranges as the library reports them, never coerced. Meta discloses spend and impressions for political and issue Ads, and reach (`audience`) for some EU Ads; otherwise null. TikTok reports `audience` (a bucket such as `10K-100K`); `spend` is null, and `impressions` is null except on a single-Ad lookup.
        - `impressions` string required nullable
        - `spend` string required nullable
        - `currency` string required nullable
        - `audience` string required nullable
      - `kind` string nullable one of `commercial`, `political`, `issue` Meta and Google.
      - `media_type` string nullable one of `video`, `image`, `carousel`, `text` Meta and Google (`video`, `image`, `text`).
      - `countries` array of string nullable Countries reached, when the library discloses them: Meta and Google search and lookup, and the TikTok single-Ad lookup.
      - `variant_count` number nullable Meta only. How many Ads share this Ad's media; null for a single Ad.
      - `days_running` number nullable Meta only. Whole days from start to stop, or to now while running.
      - `variations` array of object Google only. Each version of the Ad's copy and media. A video Ad's YouTube id sits here when Google shows one, which not every video Ad has; `creative.video_urls` stays empty.
        - `headline` string required nullable
        - `description` string required nullable
        - `all_text` string required nullable
        - `destination_url` string required nullable
        - `visible_url` string required nullable
        - `image_urls` array of string required
        - `video_id` string required nullable
        - `youtube_url` string required nullable
      - `details_failed` boolean Google search only. True when this Ad's details could not be read: it holds only the list row and is not charged or stored.
      - `archived` boolean True when every media URL is our permanent archived copy; false while any is still the library's expiring URL (retried on the next sighting).
      - `winner_score` object nullable Meta only. How strongly the Ad looks like a winner. 0 to 100, computed from the stored history of the Ad, never fetched. score = round(100 x (0.7 x rank(days_running) + 0.3 x rank(variants_added))), where rank is the share of the Advertiser's other Ads below this one (ties count half) and variants_added is the variant count at the latest Snapshot minus at the first. With one Snapshot the score is round(100 x rank(days_running)), confidence low, reason single_snapshot. With no other dated Ad of the Advertiser, rank falls back to min(days, 90)/90 and min(variants, 5)/5; under 5 dated Ads of the Advertiser the score is low confidence, reason few_advertiser_ads. No start date: score null, reason no_start_date.
        - `score` number required nullable
        - `confidence` string required one of `high`, `low`
        - `inputs` object required
          - `days_running` number required nullable
          - `variants_added` number required
          - `snapshots` number required
          - `advertiser_ads` number required Dated Ads of this Advertiser stored so far, this one included.
        - `reason` string required nullable one of `single_snapshot`, `few_advertiser_ads`, `no_start_date`
      - `intelligence` any of 2 shapes nullable Present when already computed for this Ad, or asked for with `analyze`; null under `analyze` when it could not be computed.
        - Option 1 object Video Ads: the full video analysis, read from the archived video with the ad copy as context, plus `offer`, `copy`, and detected shots, the product's first appearance and the opening frame under `structure`.
          - `version` integer required
          - `model` string required
          - `computed_at` string required
          - `source` string required
          - `content` object required
            - `summary` string required
            - `niche` string required
            - `languages` array of string required
            - `content_styles` array of string required
            - `keywords` array of object required
              - `keyword` string required
              - `category` string required
              - `sentiment` number required
            - `transcript` string required nullable What is said, one line per line: the Platform's captions or speech-to-text, never the scene breakdown. Empty with no speech; null when it could not be read.
            - `transcript_source` string required nullable one of `caption`, `whisper` `caption`: the Platform's caption track. `whisper`: speech-to-text on the audio. Null when `transcript` is null, and on analysis stored before 2026-11-09.
            - `transcript_segments` array of object required nullable Timed transcript lines (ms from the start), to see when the hook lands. From the Platform's timed captions when it has them, otherwise measured from the video's audio with speech-to-text. Spoken timing here is measured; times in `structure.scenes` are approximate. Empty when the video has no speech, music-only videos included. Null when the timing could not be measured, on a YouTube Video with no captions, and on older analyses.
              - `start_ms` integer required
              - `end_ms` integer required
              - `text` string required
          - `hook` object required
            - `opening_line` string required nullable
            - `type` string required
            - `visual_hook` string required nullable
            - `rationale` string required nullable
          - `persuasion` object required
            - `call_to_action` object required
              - `type` string required
              - `text` string required nullable
            - `sponsored` boolean required Whether the video is plausibly a paid promotion. Always true when the Platform labels it an ad or a paid partnership.
            - `sponsorship_signals` array of string required
            - `brands_and_products` array of object required
              - `name` string required
              - `kind` string required
              - `prominence` string required
            - `trend_references` array of object required
              - `name` string required
              - `kind` string required
          - `tone` object required
            - `sentiment_label` string required
            - `sentiment_score` number required
            - `primary_emotion` string required
            - `emotion_intensity` number required nullable
            - `emotion_rationale` string required nullable
            - `speaking_style` string required
            - `speaking_style_notes` string required nullable
          - `visual` object required
            - `format` string required
            - `setting` string required
            - `face_visibility` string required
            - `on_screen_text` array of string required
            - `has_on_screen_captions` boolean required
            - `camera_notes` string required nullable
            - `lighting_notes` string required nullable
            - `elements` any
          - `safety` object required
            - `brand_safety_tier` string required
            - `reasons` array of string required
            - `notes` string required nullable
          - `structure` object required When things happen inside the creative: the timed scenes every analysis carries, plus the shots detected, when the product is first seen and the opening frame on a video Ad.
            - `scenes` array of object required The timed scenes, read from the stored scene breakdown. Times are approximate, to about a second. Empty when the breakdown has no timed scenes, as on a photo carousel.
              - `start_ms` integer required Approximate scene start, ms from the start.
              - `end_ms` integer required Approximate scene end, ms from the start.
              - `visual` string required nullable
              - `text_overlay` string required nullable Text shown in this scene, listed at its first appearance: text repeated from an earlier scene is left out.
              - `speech` string required nullable
              - `audio` string required nullable Notable sound effects or music.
            - `shots` array of object required nullable The detected shots in order, each from one cut to the next. A cut is a frame whose ffmpeg scene score is over 0.2. On 29 labelled Ads this found 86% of hard cuts within 500 ms (median error 67 ms) and 61% of jump cuts overall, but none on UGC Ads. Null when detection failed, and on analysis stored before shots.
              - `start_ms` integer required Shot start: the cut before it, or 0.
              - `end_ms` integer required Shot end: the next cut, or the video's end.
            - `cuts_per_minute` number required nullable Detected cuts per minute of video, to two decimals. Null whenever `shots` is.
            - `avg_shot_ms` integer required nullable The video's length over its number of shots, in ms. Null whenever `shots` is.
            - `product_first_seen_ms` integer required nullable When the promoted product first appears (the item, its packaging or logo, or an app's screen), as the start of that scene in ms, so approximate to one scene. On 26 labelled Ads that show one, it was within 3 s of the labelled time on 23 (88%). Null when no product is shown, and on analysis stored before it.
            - `first_frame` string required nullable What the video's opening frame shows, as text: a description, not a time. Read from the first scene's `**Opening Frame:**` line in the scene breakdown. Checked against the video on 10 Ads, it was accurate on all 10. Null when the breakdown has no opening frame, and on analysis stored before it.
          - `pipeline` string required always `video`
          - `offer` object required nullable The offer, in the same types as an image Ad's `offer`. Null when the extraction failed and on analysis stored before this field.
            - `type` string required
            - `text` string required nullable
          - `copy` object required nullable The persuasion angle, in the same values as an image Ad's `copy.angle`. Null when the extraction failed and on analysis stored before this field.
            - `angle` string required
            - `rationale` string required nullable
        - Option 2 object Image and carousel Ads: the analysis an image can carry, read from the archived images with the ad copy as context.
          - `pipeline` string required always `image`
          - `version` integer required
          - `model` string required
          - `computed_at` string required
          - `images_analyzed` integer required
          - `summary` string required
          - `copy` object required
            - `angle` string required
            - `rationale` string required nullable
            - `headline` string required nullable
          - `offer` object required
            - `type` string required
            - `text` string required nullable
          - `call_to_action` object required
            - `type` string required
            - `text` string required nullable
          - `visual` object required
            - `format` string required
            - `on_image_text` array of string required
          - `topics` array of string required
          - `tone` object required
            - `sentiment_label` string required
            - `sentiment_score` number required
      - `intelligence_error` object Why `analyze` left `intelligence` null: no analysis charge. Its fields are on [Errors](https://www.shortsintel.com/docs/reference/errors).
      - `advertiser_account` object nullable TikTok lookup only. The TikTok account the Ad ran from.
        - `username` string required
        - `display_name` string required nullable
        - `followers` string required nullable As TikTok shows it (`9.3M`), not a number.
        - `verified` boolean required nullable
        - `url` string required nullable
      - `targeting` object nullable TikTok lookup only. Where and to whom the ad library says the Ad was delivered, impressions as ranges per country by age and gender. Null when the Ad came from Creative Center.
        - `countries` array of string required
        - `impressions` string required nullable
        - `audience_size` string required nullable
        - `ages` array of string required Age ranges targeted (`18-24`).
        - `genders` array of string required Genders targeted (`female`, `male`, `unknown`).
        - `by_country` array of object required
          - `country` string required
          - `impressions` string required nullable
          - `breakdown` array of object required
            - `age` string required
            - `gender` string required
            - `impressions` string required nullable
      - `performance` object nullable TikTok lookup only. Creative Center's engagement figures, when the Ad is a Creative Center Top Ad; null for an Ad only the ad library holds.
        - `ctr` number required nullable Click-through rate as a fraction (0.46 is 46%).
        - `likes` number required nullable
        - `comments` number required nullable
        - `shares` number required nullable
      - `objective` string nullable TikTok lookup only. The Ad's objective (`Traffic`).
      - `removed_from_library` boolean required True when the library no longer shows the Ad and it was served from our stored copy.
      - `removed_at` string required nullable When this API first found the Ad gone from the library.
    - `as_of` string required nullable When the Ad was last observed in the library.
- Option 2 object Returned instead of a Run when `estimate=true`. Nothing is charged. Operations with more to say about a quote add members; the two above are always present.
  - `estimate_credits` integer required What the call would be charged, in Credits.
  - `cached` boolean required Whether a stored result can already answer. It does not change the price.

### 202 Accepted

Still running: `{ run }`. Poll `GET /v1/runs/{id}`.

object

Still running. Poll `poll_url` (`GET /v1/runs/{id}`), waiting the 202's `Retry-After` seconds between polls.

- `run` object required Every charged operation creates a Run. The envelope is identical whether the result came back inline or was polled. Its fields are on [Poll or re-read a run](https://www.shortsintel.com/docs/reference/runs/get-run#response-200).
- `poll_url` string required format `uri` Absolute URL of `GET /v1/runs/{id}` for this Run, the body's copy of the 202's `Location` header for callers that never see headers (MCP). Present only while the Run is `running`.

202 · Example

```json
{
  "run": {
    "id": "run_8f2c1ad04b",
    "kind": "ad_lookup",
    "status": "running",
    "created_at": "2026-09-08T09:14:02.418Z",
    "completed_at": null,
    "charge_credits": 0,
    "list_price_credits": 0,
    "cached": false
  },
  "poll_url": "https://www.shortsintel.com/v1/runs/run_8f2c1ad04b"
}
```

### 400 Bad Request

`invalid_request` (a malformed id, `wait`, `estimate` or Idempotency-Key) or `unsupported_platform`, refused before any Run opens. Nothing is charged.

Codes: `invalid_request`, `unsupported_platform`. The problem body and every code are on [Errors](https://www.shortsintel.com/docs/reference/errors).

### 401 Unauthorized

Codes: `missing_authorization`, `invalid_key`, `key_revoked`, `key_expired`, `invalid_token`. The problem body and every code are on [Errors](https://www.shortsintel.com/docs/reference/errors).

### 402 Payment Required

`insufficient_balance` — the Balance cannot cover the quoted price (the worst case for Research and enriched search). No Run is created and nothing is charged. A multi-item Run whose Balance runs short later charges its completed items up to the Balance and ends `partial`.

Code: `insufficient_balance`. The problem body and every code are on [Errors](https://www.shortsintel.com/docs/reference/errors).

### 409 Conflict

`idempotency_conflict` — the same Idempotency-Key with a different payload.

Code: `idempotency_conflict`. The problem body and every code are on [Errors](https://www.shortsintel.com/docs/reference/errors).

### 429 Too Many Requests

`rate_limit_exceeded` or `concurrency_limit_exceeded`, with `Retry-After`.

Codes: `rate_limit_exceeded`, `concurrency_limit_exceeded`. The problem body and every code are on [Errors](https://www.shortsintel.com/docs/reference/errors).

### 502 Bad Gateway

A Run that opened and then failed is answered as `application/problem+json` with its problem's status — `vendor_error` here, `not_found` as 404 — and the Run envelope alongside, so the Run id is kept. A failed Run is never charged.

Code: `vendor_error`. The problem body and every code are on [Errors](https://www.shortsintel.com/docs/reference/errors).

### 503 Service Unavailable

`source_unavailable`: every source this Run reads failed or is switched off. The Run fails uncharged, with its envelope alongside; `endpoints` names the source. Wait `retry_after_seconds` (also sent as `Retry-After`) and retry rather than treating it as empty.

Code: `source_unavailable`. The problem body and every code are on [Errors](https://www.shortsintel.com/docs/reference/errors).

## Related guides

This call creates a Run. With `wait` it holds the response open until the Run finishes or the wait runs out, then answers with the finished result or `202` and the running Run. Poll [`GET /v1/runs/{id}`](https://www.shortsintel.com/docs/reference/runs/get-run) until it settles; re-reading a Run is free.

- [Runs](https://www.shortsintel.com/docs/concepts/runs): How a call becomes a Run, waiting inline, polling and free re-reads.
- [Pricing and estimates](https://www.shortsintel.com/docs/concepts/pricing-and-estimates): Credits, estimates and why a cached result costs the same as a fresh one.
- [Ads](https://www.shortsintel.com/docs/concepts/ads): Search Meta's, TikTok's and Google's ad libraries: the filters, what each Ad carries, the winner Score, Ad analysis, what is stored, and what a search costs.

## Request samples

curl:

```bash
curl "https://www.shortsintel.com/v1/ads/google/creative_id_example?analyze=true&wait=30" \
  -H "Authorization: Bearer sk_live_YOUR_KEY"
```

TypeScript:

```typescript
const response = await fetch(
  "https://www.shortsintel.com/v1/ads/google/creative_id_example?analyze=true&wait=30",
  {
    method: "GET",
    headers: {
      "Authorization": "Bearer sk_live_YOUR_KEY",
    },
  },
);

if (!response.ok) {
  throw new Error(`ShortsIntel ${response.status}`);
}

const data = await response.json();
```

Python:

```python
import requests

response = requests.get(
    "https://www.shortsintel.com/v1/ads/google/creative_id_example?analyze=true&wait=30",
    headers={
        "Authorization": "Bearer sk_live_YOUR_KEY",
    },
)
response.raise_for_status()

data = response.json()
```

200 · The Run finished inside the wait budget: `{ run, result }`.

```json
{
  "run": {
    "id": "run_8f2c1ad04b",
    "kind": "ad_lookup",
    "status": "completed",
    "created_at": "2026-09-08T09:14:02.418Z",
    "completed_at": "2026-09-08T09:14:04.930Z",
    "charge_credits": 3,
    "list_price_credits": 3,
    "cached": false
  },
  "result": {
    "ad": {
      "library": "google",
      "id": "CR12688769706757193729",
      "url": "https://adstransparency.google.com/advertiser/AR18378488041124659201/creative/CR12688769706757193729",
      "advertiser": {
        "id": "AR18378488041124659201",
        "name": "Northpeak Outdoor GmbH"
      },
      "is_active": true,
      "first_shown": "2026-06-20T18:21:31.000Z",
      "last_shown": "2026-11-06T22:14:25.000Z",
      "placements": [
        "youtube"
      ],
      "creative": {
        "title": "New winter gear, in store now.",
        "body": "Free returns on every order.",
        "cta": null,
        "link_url": "https://www.example.com/winter",
        "format": "video",
        "image_urls": [],
        "video_urls": [],
        "thumbnail_urls": []
      },
      "reach": {
        "impressions": "10000-15000",
        "spend": null,
        "currency": null,
        "audience": null
      },
      "kind": "commercial",
      "media_type": "video",
      "countries": [
        "DE"
      ],
      "variations": [
        {
          "headline": "New winter gear, in store now.",
          "description": "Free returns on every order.",
          "all_text": null,
          "destination_url": "https://www.example.com/winter",
          "visible_url": "example.com",
          "image_urls": [],
          "video_id": "dQw4w9WgXcQ",
          "youtube_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
        }
      ],
      "archived": true,
      "removed_from_library": false,
      "removed_at": null
    },
    "as_of": "2026-11-07T09:12:58.104Z"
  }
}
```

200 · With `estimate=true`: the price, no Run, no charge.

```json
{
  "estimate_credits": 3,
  "cached": false
}
```

[Previous Get a TikTok Ad](https://www.shortsintel.com/docs/reference/ads/get-tik-tok-ad)[Next Get a Meta Ad's analysis](https://www.shortsintel.com/docs/reference/ads/get-meta-ad-intelligence)
