{
  "openapi": "3.1.0",
  "info": {
    "title": "Public Ads API",
    "version": "1.0.0",
    "summary": "Search the public ad libraries of Meta, Google, TikTok and LinkedIn.",
    "description": "One endpoint per platform, `GET /v1/{platform}/ads`, each taking only the filters its library has, and `GET /v1/ads/{id}` for one ad with all its detail. Every platform returns the same `Ad` record.\n\nAuthenticate with `Authorization: Bearer <key>`. Sandbox keys (`pads_test_`) return sample ads and never use credits; live keys (`pads_live_`) read the platform. An AI agent can get both itself: `POST /v1/agent/signup`.\n\nEvery error is `{ \"error\": { \"code\", \"message\" } }`. No error, empty page or sandbox call uses credits.\n\nPublic Ads API is not affiliated with Meta, Google, TikTok or LinkedIn.",
    "contact": {
      "name": "Public Ads API",
      "url": "https://publicadsapi.com/contact"
    },
    "termsOfService": "https://publicadsapi.com/legal/terms"
  },
  "servers": [
    {
      "url": "https://api.publicadsapi.com",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "API documentation",
    "url": "https://publicadsapi.com/docs"
  },
  "security": [
    {
      "bearer": []
    }
  ],
  "tags": [
    {
      "name": "Search",
      "description": "One endpoint per platform."
    },
    {
      "name": "Ads",
      "description": "One ad with all its detail."
    },
    {
      "name": "Agents",
      "description": "Keys for AI agents, with no person signing up."
    },
    {
      "name": "Health",
      "description": "Whether the API is up."
    }
  ],
  "paths": {
    "/v1/meta/ads": {
      "get": {
        "summary": "Search the Meta Ad Library",
        "operationId": "searchMetaAds",
        "tags": [
          "Search"
        ],
        "description": "One page of Meta Ad Library ads, up to 30, each the same `Ad` record every platform returns. Send exactly one of `q`, `page_id`. A parameter this platform doesn't take is 400 INVALID_INPUT naming it. Uses 1 credit on a live key when the page has ads; an empty page, an error and every sandbox call use none. Live calls read the platform as they run (10 to 40 seconds): set a 60 second timeout. Docs: https://publicadsapi.com/docs/api/meta",
        "externalDocs": {
          "url": "https://publicadsapi.com/docs/api/meta"
        },
        "x-one-of-required": [
          "q",
          "page_id"
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "A keyword, matched against the ad's text as Meta's own search box does. Also accepted as `query` or `keyword`. Send exactly one of q, page_id.",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "example": "running shoes",
            "x-aliases": [
              "query",
              "keyword"
            ]
          },
          {
            "name": "page_id",
            "in": "query",
            "required": false,
            "description": "One advertiser's ads: its Facebook page id, the `advertiser.id` of any of its Meta ads. Send exactly one of q, page_id.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{1,30}$"
            },
            "example": "100000000000001"
          },
          {
            "name": "exact",
            "in": "query",
            "required": false,
            "description": "`true` matches `q` as a whole phrase. Default `false`.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "description": "Where the ad was shown: an ISO 3166-1 alpha-2 code such as `GB`, `US` or `DE`, or `ALL`. Two letters that are no country (`ZZ`) are refused. Default `ALL`. Also accepted as `country_code`.",
            "schema": {
              "type": "string",
              "pattern": "^([A-Za-z]{2}|[Aa][Ll][Ll])$",
              "default": "ALL"
            },
            "example": "GB",
            "x-aliases": [
              "country_code"
            ]
          },
          {
            "name": "active",
            "in": "query",
            "required": false,
            "description": "`true`: only ads running now. `false`: only ads that have stopped. Left out: both.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "Meta's media filter. `none` is ads with no image or video. Left out: every format. One of: `image`, `video`, `meme`, `none`. Also accepted as `media_type`.",
            "schema": {
              "type": "string",
              "enum": [
                "image",
                "video",
                "meme",
                "none"
              ]
            },
            "x-aliases": [
              "media_type"
            ]
          },
          {
            "name": "placements",
            "in": "query",
            "required": false,
            "description": "Comma-separated: ads that ran on any of these. Left out: every placement. Values: `facebook`, `instagram`, `messenger`, `audience_network`, `threads`.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "facebook",
                  "instagram",
                  "messenger",
                  "audience_network",
                  "threads"
                ]
              }
            },
            "style": "form",
            "explode": false,
            "example": [
              "facebook",
              "instagram"
            ]
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Meta's ad category. `political_and_issue` ads carry spend and impressions ranges. One of: `all`, `political_and_issue`, `housing`, `employment`, `financial`. Default `all`.",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "political_and_issue",
                "housing",
                "employment",
                "financial"
              ],
              "default": "all"
            }
          },
          {
            "name": "language",
            "in": "query",
            "required": false,
            "description": "The ad's language, ISO 639-1 such as `en`. Left out: every language.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z]{2}$"
            },
            "example": "en"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "`impressions`: most seen first, as Meta ranks them. `newest`: most recently started first. One of: `impressions`, `newest`. Default `impressions`.",
            "schema": {
              "type": "string",
              "enum": [
                "impressions",
                "newest"
              ],
              "default": "impressions"
            }
          },
          {
            "name": "date_from",
            "in": "query",
            "required": false,
            "description": "Ads shown on or after this day, `YYYY-MM-DD`.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-01-01"
          },
          {
            "name": "date_to",
            "in": "query",
            "required": false,
            "description": "Ads shown on or before this day, `YYYY-MM-DD`. Not before `date_from`.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-21"
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The `next_cursor` of the page before. Send it with exactly the same other parameters: a cursor from a different search is `400 INVALID_INPUT`.",
            "schema": {
              "type": "string",
              "maxLength": 2000
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "`raw` adds each ad's own record from the platform as `raw`, for fields we don't map. Values: `raw`.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "raw"
                ]
              }
            },
            "style": "form",
            "explode": false,
            "example": [
              "raw"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "A page of ads (possibly empty).",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-Credits-Used": {
                "$ref": "#/components/headers/X-Credits-Used"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MetaSearchResponse"
                },
                "example": {
                  "platform": "meta",
                  "query": {
                    "q": "running shoes",
                    "page_id": null,
                    "exact": false,
                    "country": "GB",
                    "active": null,
                    "format": null,
                    "placements": null,
                    "category": "all",
                    "language": null,
                    "sort": "impressions",
                    "date_from": null,
                    "date_to": null
                  },
                  "ads": [
                    {
                      "id": "meta_900000000000001",
                      "platform": "meta",
                      "platform_ad_id": "900000000000001",
                      "library_url": "https://www.facebook.com/ads/library/?id=900000000000001",
                      "advertiser": {
                        "id": "100000000000001",
                        "name": "Northwind Running Co",
                        "domain": null,
                        "library_url": "https://www.facebook.com/ads/library/?view_all_page_id=100000000000001"
                      },
                      "status": "active",
                      "first_shown": "2026-08-04T07:00:00Z",
                      "last_shown": "2026-10-18T07:00:00Z",
                      "format": "image",
                      "text": {
                        "headline": "Trail shoes, half price this week",
                        "body": "Grip that holds on wet rock. Free returns for 60 days.",
                        "description": null,
                        "cta": "Shop now"
                      },
                      "text_source": "platform",
                      "link_url": "https://northwind-running.example/trail",
                      "media": [
                        {
                          "type": "image",
                          "url": "https://example.com/sandbox-media/meta-trail.jpg",
                          "preview_url": null,
                          "width": 1080,
                          "height": 1080
                        }
                      ],
                      "placements": [
                        "facebook",
                        "instagram",
                        "audience_network",
                        "messenger"
                      ],
                      "regions": null,
                      "metrics": {
                        "impressions": null,
                        "reach": null,
                        "spend": null
                      },
                      "variants": 1
                    }
                  ],
                  "total_estimate": 5,
                  "has_more": true,
                  "next_cursor": "eyJwIjoibWV0YSIsImMiOiIzIiwicSI6IldZZVBvV0xGd1Bkei1OejUifQ",
                  "credits_used": 0,
                  "credits_remaining": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidInput"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/QuotaExceeded"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamChanged"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "504": {
            "$ref": "#/components/responses/Timeout"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "source": "curl \"https://api.publicadsapi.com/v1/meta/ads?q=running+shoes&country=GB\" \\\n  -H \"Authorization: Bearer $PADS_API_KEY\""
          }
        ]
      }
    },
    "/v1/google/ads": {
      "get": {
        "summary": "Search the Google Ads Transparency Center",
        "operationId": "searchGoogleAds",
        "tags": [
          "Search"
        ],
        "description": "One page of Google Ads Transparency Center ads, up to 100, each the same `Ad` record every platform returns. Send exactly one of `advertiser_id`, `domain`. A parameter this platform doesn't take is 400 INVALID_INPUT naming it. Uses 1 credit on a live key when the page has ads; an empty page, an error and every sandbox call use none. Live calls read the platform as they run (10 to 40 seconds): set a 60 second timeout. Docs: https://publicadsapi.com/docs/api/google",
        "externalDocs": {
          "url": "https://publicadsapi.com/docs/api/google"
        },
        "x-one-of-required": [
          "advertiser_id",
          "domain"
        ],
        "parameters": [
          {
            "name": "advertiser_id",
            "in": "query",
            "required": false,
            "description": "Google's advertiser id: `AR` and digits, as in the advertiser's Transparency Center address. Send exactly one of advertiser_id, domain.",
            "schema": {
              "type": "string",
              "pattern": "^AR[0-9]{5,30}$"
            },
            "example": "AR10000000000000000001"
          },
          {
            "name": "domain",
            "in": "query",
            "required": false,
            "description": "The advertiser's verified domain, such as `example.com`. An advertiser that verified under another domain returns no ads for this one: try its `advertiser_id`. Send exactly one of advertiser_id, domain.",
            "schema": {
              "type": "string",
              "pattern": "^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,63}$"
            },
            "example": "tailspin-travel.example"
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "description": "Where the ad was shown: an ISO 3166-1 alpha-2 code, or `ALL`. Two letters that are no country (`ZZ`) are refused. Default `ALL`. Also accepted as `country_code`.",
            "schema": {
              "type": "string",
              "pattern": "^([A-Za-z]{2}|[Aa][Ll][Ll])$",
              "default": "ALL"
            },
            "example": "GB",
            "x-aliases": [
              "country_code"
            ]
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "Left out: every format. Text ads come back as an image in `media`; their `text` may be `null` (`text_source: \"ocr\"` where it was read from the image). One of: `text`, `image`, `video`. Also accepted as `media_type`.",
            "schema": {
              "type": "string",
              "enum": [
                "text",
                "image",
                "video"
              ]
            },
            "x-aliases": [
              "media_type"
            ]
          },
          {
            "name": "surface",
            "in": "query",
            "required": false,
            "description": "Where on Google the ad ran. Left out: everywhere. One of: `search`, `youtube`, `shopping`, `maps`, `play`.",
            "schema": {
              "type": "string",
              "enum": [
                "search",
                "youtube",
                "shopping",
                "maps",
                "play"
              ]
            }
          },
          {
            "name": "political",
            "in": "query",
            "required": false,
            "description": "`true`: political ads only. Needs a `country`, as Google lists political ads per country. Default `false`.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "date_from",
            "in": "query",
            "required": false,
            "description": "Ads shown on or after this day, `YYYY-MM-DD`.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-01-01"
          },
          {
            "name": "date_to",
            "in": "query",
            "required": false,
            "description": "Ads shown on or before this day, `YYYY-MM-DD`. Not before `date_from`.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-21"
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The `next_cursor` of the page before. Send it with exactly the same other parameters: a cursor from a different search is `400 INVALID_INPUT`.",
            "schema": {
              "type": "string",
              "maxLength": 2000
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "`raw` adds each ad's own record from the platform as `raw`, for fields we don't map. Values: `raw`.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "raw"
                ]
              }
            },
            "style": "form",
            "explode": false,
            "example": [
              "raw"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "A page of ads (possibly empty).",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-Credits-Used": {
                "$ref": "#/components/headers/X-Credits-Used"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GoogleSearchResponse"
                },
                "example": {
                  "platform": "google",
                  "query": {
                    "advertiser_id": null,
                    "domain": "tailspin-travel.example",
                    "country": "GB",
                    "format": null,
                    "surface": null,
                    "political": false,
                    "date_from": null,
                    "date_to": null
                  },
                  "ads": [
                    {
                      "id": "google_AR10000000000000000001_CR20000000000000000001",
                      "platform": "google",
                      "platform_ad_id": "CR20000000000000000001",
                      "library_url": "https://adstransparency.google.com/advertiser/AR10000000000000000001/creative/CR20000000000000000001",
                      "advertiser": {
                        "id": "AR10000000000000000001",
                        "name": "Tailspin Travel Ltd",
                        "domain": "tailspin-travel.example",
                        "library_url": "https://adstransparency.google.com/advertiser/AR10000000000000000001"
                      },
                      "status": "unknown",
                      "first_shown": "2025-10-13T00:00:00Z",
                      "last_shown": "2026-10-18T00:00:00Z",
                      "format": "text",
                      "text": {
                        "headline": "Tailspin Travel | City breaks from £99",
                        "body": "Flights and hotel together. ATOL protected. Book by Sunday.",
                        "description": null,
                        "cta": null
                      },
                      "text_source": "ocr",
                      "link_url": "https://tailspin-travel.example/city-breaks",
                      "media": [
                        {
                          "type": "image",
                          "url": "https://example.com/sandbox-media/google-text.png",
                          "preview_url": null,
                          "width": 380,
                          "height": 120
                        }
                      ],
                      "placements": [
                        "search"
                      ],
                      "regions": [
                        "GB",
                        "DE",
                        "IE"
                      ],
                      "metrics": {
                        "impressions": null,
                        "reach": null,
                        "spend": null
                      },
                      "variants": 4
                    }
                  ],
                  "total_estimate": 4,
                  "has_more": true,
                  "next_cursor": "eyJwIjoiZ29vZ2xlIiwiYyI6IjMiLCJxIjoidjNNaXRtc2dDcWlNR3BjWSJ9",
                  "credits_used": 0,
                  "credits_remaining": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidInput"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/QuotaExceeded"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamChanged"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "504": {
            "$ref": "#/components/responses/Timeout"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "source": "curl \"https://api.publicadsapi.com/v1/google/ads?domain=tailspin-travel.example&country=GB\" \\\n  -H \"Authorization: Bearer $PADS_API_KEY\""
          }
        ]
      }
    },
    "/v1/tiktok/ads": {
      "get": {
        "summary": "Search the TikTok Commercial Content Library",
        "operationId": "searchTikTokAds",
        "tags": [
          "Search"
        ],
        "description": "One page of TikTok Commercial Content Library ads, up to 36, each the same `Ad` record every platform returns. Send exactly one of `q`, `advertiser`. A parameter this platform doesn't take is 400 INVALID_INPUT naming it. Uses 1 credit on a live key when the page has ads; an empty page, an error and every sandbox call use none. Live calls read the platform as they run (10 to 40 seconds): set a 60 second timeout. Docs: https://publicadsapi.com/docs/api/tiktok",
        "externalDocs": {
          "url": "https://publicadsapi.com/docs/api/tiktok"
        },
        "x-one-of-required": [
          "q",
          "advertiser"
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "A keyword. It also matches creators' posts that mention a brand; for one advertiser's ads, use `advertiser`. Also accepted as `query` or `keyword`. Send exactly one of q, advertiser.",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "example": "pottery",
            "x-aliases": [
              "query",
              "keyword"
            ]
          },
          {
            "name": "advertiser",
            "in": "query",
            "required": false,
            "description": "An advertiser's exact name, as TikTok shows it (the `advertiser.name` of any of its ads). TikTok matches advertisers by name: it can't search by business id. Not yet confirmed against TikTok's live library (October 2026); if it brings nothing back, search the name with `q`. Send exactly one of q, advertiser.",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "example": "Fabrikam Studio"
          },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "description": "Where the ad was shown: a country of the EEA, `GB` or `CH`. TikTok's library covers no others, so there are no US ads. Default `GB`. Also accepted as `country_code`.",
            "schema": {
              "type": "string",
              "enum": [
                "AT",
                "BE",
                "BG",
                "HR",
                "CY",
                "CZ",
                "DK",
                "EE",
                "FI",
                "FR",
                "DE",
                "GR",
                "HU",
                "IE",
                "IT",
                "LV",
                "LT",
                "LU",
                "MT",
                "NL",
                "PL",
                "PT",
                "RO",
                "SK",
                "SI",
                "ES",
                "SE",
                "IS",
                "LI",
                "NO",
                "GB",
                "CH"
              ],
              "default": "GB"
            },
            "x-aliases": [
              "country_code"
            ]
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "`last_shown`: most recently seen first. `first_shown`: most recently started first. One of: `last_shown`, `first_shown`. Default `last_shown`.",
            "schema": {
              "type": "string",
              "enum": [
                "last_shown",
                "first_shown"
              ],
              "default": "last_shown"
            }
          },
          {
            "name": "date_from",
            "in": "query",
            "required": false,
            "description": "Ads shown on or after this day, `YYYY-MM-DD`. Default a year before `date_to`.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-01-01"
          },
          {
            "name": "date_to",
            "in": "query",
            "required": false,
            "description": "Ads shown on or before this day, `YYYY-MM-DD`. Not before `date_from`. Default today.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-21"
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The `next_cursor` of the page before. Send it with exactly the same other parameters: a cursor from a different search is `400 INVALID_INPUT`.",
            "schema": {
              "type": "string",
              "maxLength": 2000
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "`raw` adds each ad's own record from the platform as `raw`, for fields we don't map. Values: `raw`.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "raw"
                ]
              }
            },
            "style": "form",
            "explode": false,
            "example": [
              "raw"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "A page of ads (possibly empty).",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-Credits-Used": {
                "$ref": "#/components/headers/X-Credits-Used"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TikTokSearchResponse"
                },
                "example": {
                  "platform": "tiktok",
                  "query": {
                    "q": "pottery",
                    "advertiser": null,
                    "country": "GB",
                    "sort": "last_shown",
                    "date_from": "2025-10-21",
                    "date_to": "2026-10-21"
                  },
                  "ads": [
                    {
                      "id": "tiktok_7400000000000000001",
                      "platform": "tiktok",
                      "platform_ad_id": "7400000000000000001",
                      "library_url": "https://library.tiktok.com/ads/detail/?ad_id=7400000000000000001",
                      "advertiser": {
                        "id": "7000000000000000001",
                        "name": "Fabrikam Studio",
                        "domain": null,
                        "library_url": null
                      },
                      "status": "unknown",
                      "first_shown": "2026-08-20T00:00:00Z",
                      "last_shown": "2026-10-16T00:00:00Z",
                      "format": "video",
                      "text": {
                        "headline": "Glaze your own mug this weekend",
                        "body": null,
                        "description": null,
                        "cta": "Book now"
                      },
                      "text_source": "platform",
                      "link_url": "https://fabrikam-studio.example/workshops",
                      "media": [
                        {
                          "type": "video",
                          "url": "https://example.com/sandbox-media/tiktok-1.mp4",
                          "preview_url": "https://example.com/sandbox-media/tiktok-1.jpg",
                          "width": 720,
                          "height": 1280
                        }
                      ],
                      "placements": [
                        "tiktok"
                      ],
                      "regions": [
                        "GB"
                      ],
                      "metrics": {
                        "impressions": null,
                        "reach": {
                          "min": 1000,
                          "max": 10000
                        },
                        "spend": null
                      },
                      "variants": null
                    }
                  ],
                  "total_estimate": 3,
                  "has_more": false,
                  "next_cursor": null,
                  "credits_used": 0,
                  "credits_remaining": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidInput"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/QuotaExceeded"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamChanged"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "504": {
            "$ref": "#/components/responses/Timeout"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "source": "curl \"https://api.publicadsapi.com/v1/tiktok/ads?q=pottery&country=GB\" \\\n  -H \"Authorization: Bearer $PADS_API_KEY\""
          }
        ]
      }
    },
    "/v1/linkedin/ads": {
      "get": {
        "summary": "Search the LinkedIn Ad Library",
        "operationId": "searchLinkedInAds",
        "tags": [
          "Search"
        ],
        "description": "One page of LinkedIn Ad Library ads, about 24, each the same `Ad` record every platform returns. Send exactly one of `q`, `company_id`, `company`. A parameter this platform doesn't take is 400 INVALID_INPUT naming it. Uses 2 credits on a live key when the page has ads; an empty page, an error and every sandbox call use none. Live calls read the platform as they run (10 to 40 seconds): set a 60 second timeout. Docs: https://publicadsapi.com/docs/api/linkedin",
        "externalDocs": {
          "url": "https://publicadsapi.com/docs/api/linkedin"
        },
        "x-one-of-required": [
          "q",
          "company_id",
          "company"
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "A keyword, matched against the ad's text. Also accepted as `query` or `keyword`. Send exactly one of q, company_id, company.",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "example": "analytics",
            "x-aliases": [
              "query",
              "keyword"
            ]
          },
          {
            "name": "company_id",
            "in": "query",
            "required": false,
            "description": "LinkedIn's numeric company id, the `advertiser.id` of any of its ads. Send exactly one of q, company_id, company.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{1,30}$"
            },
            "example": "80000001"
          },
          {
            "name": "company",
            "in": "query",
            "required": false,
            "description": "A company name, matched as LinkedIn's advertiser search does. Send exactly one of q, company_id, company.",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "example": "Contoso"
          },
          {
            "name": "countries",
            "in": "query",
            "required": false,
            "description": "Comma-separated ISO 3166-1 alpha-2 codes: ads shown in any of them. Left out: every country. Two letters that are no country (`ZZ`) are refused. Also accepted as `country` or `country_code`.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "pattern": "^[A-Za-z]{2}$"
              }
            },
            "style": "form",
            "explode": false,
            "example": [
              "GB",
              "NL"
            ],
            "x-aliases": [
              "country",
              "country_code"
            ]
          },
          {
            "name": "date_from",
            "in": "query",
            "required": false,
            "description": "Ads shown on or after this day, `YYYY-MM-DD`. Within the last year: LinkedIn's library goes back no further.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-01-01"
          },
          {
            "name": "date_to",
            "in": "query",
            "required": false,
            "description": "Ads shown on or before this day, `YYYY-MM-DD`. Within the last year.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-21"
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The `next_cursor` of the page before. Send it with exactly the same other parameters: a cursor from a different search is `400 INVALID_INPUT`.",
            "schema": {
              "type": "string",
              "maxLength": 2000
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "`raw` adds each ad's own record from the platform as `raw`, for fields we don't map. Values: `raw`.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "raw"
                ]
              }
            },
            "style": "form",
            "explode": false,
            "example": [
              "raw"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "A page of ads (possibly empty).",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-Credits-Used": {
                "$ref": "#/components/headers/X-Credits-Used"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkedInSearchResponse"
                },
                "example": {
                  "platform": "linkedin",
                  "query": {
                    "q": null,
                    "company_id": null,
                    "company": "Contoso",
                    "countries": null,
                    "date_from": null,
                    "date_to": null
                  },
                  "ads": [
                    {
                      "id": "linkedin_500000001",
                      "platform": "linkedin",
                      "platform_ad_id": "500000001",
                      "library_url": "https://www.linkedin.com/ad-library/detail/500000001",
                      "advertiser": {
                        "id": "80000001",
                        "name": "Contoso Analytics",
                        "domain": null,
                        "library_url": "https://www.linkedin.com/ad-library/search?companyIds=80000001"
                      },
                      "status": "unknown",
                      "first_shown": "2026-07-01T00:00:00Z",
                      "last_shown": "2026-10-15T00:00:00Z",
                      "format": "image",
                      "text": {
                        "headline": "Dashboards your finance team will actually open",
                        "body": "See how 300 finance teams close the month two days sooner.",
                        "description": null,
                        "cta": "Request demo"
                      },
                      "text_source": "platform",
                      "link_url": "https://contoso-analytics.example/demo",
                      "media": [
                        {
                          "type": "image",
                          "url": "https://example.com/sandbox-media/linkedin-dash.png",
                          "preview_url": null,
                          "width": 1200,
                          "height": 627
                        }
                      ],
                      "placements": [
                        "linkedin"
                      ],
                      "regions": [
                        "GB",
                        "NL"
                      ],
                      "metrics": {
                        "impressions": {
                          "min": 50000,
                          "max": 100000
                        },
                        "reach": null,
                        "spend": null
                      },
                      "variants": null
                    }
                  ],
                  "total_estimate": 3,
                  "has_more": false,
                  "next_cursor": null,
                  "credits_used": 0,
                  "credits_remaining": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidInput"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/QuotaExceeded"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamChanged"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "504": {
            "$ref": "#/components/responses/Timeout"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "source": "curl \"https://api.publicadsapi.com/v1/linkedin/ads?company=Contoso\" \\\n  -H \"Authorization: Bearer $PADS_API_KEY\""
          }
        ]
      }
    },
    "/v1/ads/{id}": {
      "get": {
        "summary": "One ad with all its detail",
        "operationId": "getAd",
        "tags": [
          "Ads"
        ],
        "description": "The Ad record plus `detail`: every version, each country with its dates and impressions, targeting, the EU's reach figures (Meta) and who paid. The rich parts are for ads shown in the EU. Uses the platform's credits when the ad is found (meta 1, google 1, tiktok 1, linkedin 2); 404 AD_NOT_FOUND uses none. Docs: https://publicadsapi.com/docs/api/ads",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Our id from a search.",
            "schema": {
              "type": "string",
              "pattern": "^(meta|google|tiktok|linkedin)_[A-Za-z0-9_-]+$"
            },
            "example": "meta_900000000000001"
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "`raw` adds the platform's own record as `raw`.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "raw"
                ]
              }
            },
            "style": "form",
            "explode": false
          }
        ],
        "responses": {
          "200": {
            "description": "The ad.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-Credits-Used": {
                "$ref": "#/components/headers/X-Credits-Used"
              },
              "X-Credits-Remaining": {
                "$ref": "#/components/headers/X-Credits-Remaining"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdResponse"
                },
                "example": {
                  "id": "meta_900000000000001",
                  "platform": "meta",
                  "platform_ad_id": "900000000000001",
                  "library_url": "https://www.facebook.com/ads/library/?id=900000000000001",
                  "advertiser": {
                    "id": "100000000000001",
                    "name": "Northwind Running Co",
                    "domain": null,
                    "library_url": "https://www.facebook.com/ads/library/?view_all_page_id=100000000000001"
                  },
                  "status": "active",
                  "first_shown": "2026-08-04T07:00:00Z",
                  "last_shown": "2026-10-18T07:00:00Z",
                  "format": "image",
                  "text": {
                    "headline": "Trail shoes, half price this week",
                    "body": "Grip that holds on wet rock. Free returns for 60 days.",
                    "description": null,
                    "cta": "Shop now"
                  },
                  "text_source": "platform",
                  "link_url": "https://northwind-running.example/trail",
                  "media": [
                    {
                      "type": "image",
                      "url": "https://example.com/sandbox-media/meta-trail.jpg",
                      "preview_url": null,
                      "width": 1080,
                      "height": 1080
                    }
                  ],
                  "placements": [
                    "facebook",
                    "instagram",
                    "audience_network",
                    "messenger"
                  ],
                  "regions": null,
                  "metrics": {
                    "impressions": null,
                    "reach": null,
                    "spend": null
                  },
                  "variants": 1,
                  "detail": {
                    "variants": [
                      {
                        "text": {
                          "headline": "Trail shoes, half price this week",
                          "body": "Grip that holds on wet rock. Free returns for 60 days.",
                          "description": null,
                          "cta": "Shop now"
                        },
                        "media": [
                          {
                            "type": "image",
                            "url": "https://example.com/sandbox-media/meta-trail.jpg",
                            "preview_url": null,
                            "width": 1080,
                            "height": 1080
                          }
                        ]
                      }
                    ],
                    "regions": [],
                    "targeting": {
                      "age_min": 18,
                      "age_max": 54,
                      "genders": [
                        "female",
                        "male"
                      ],
                      "locations": [
                        "DE",
                        "FR",
                        "NL"
                      ],
                      "used": null
                    },
                    "eu_reach": {
                      "total": 46876,
                      "by_country": [
                        {
                          "country": "DE",
                          "reach": 24310
                        },
                        {
                          "country": "FR",
                          "reach": 14002
                        },
                        {
                          "country": "NL",
                          "reach": 8564
                        }
                      ],
                      "by_age_gender": [
                        {
                          "country": "DE",
                          "age_range": "18-24",
                          "male": 3120,
                          "female": 2904,
                          "unknown": 41
                        },
                        {
                          "country": "DE",
                          "age_range": "25-34",
                          "male": 6870,
                          "female": 7215,
                          "unknown": 88
                        },
                        {
                          "country": "FR",
                          "age_range": "25-34",
                          "male": 4011,
                          "female": 4390,
                          "unknown": 22
                        }
                      ]
                    },
                    "payer": {
                      "payer": "Northwind Running Co",
                      "beneficiary": "Northwind Running Co"
                    }
                  },
                  "credits_used": 1,
                  "credits_remaining": 99
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidInput"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/QuotaExceeded"
          },
          "404": {
            "$ref": "#/components/responses/AdNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "502": {
            "$ref": "#/components/responses/UpstreamChanged"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "504": {
            "$ref": "#/components/responses/Timeout"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "source": "curl \"https://api.publicadsapi.com/v1/ads/meta_900000000000001\" \\\n  -H \"Authorization: Bearer $PADS_API_KEY\""
          }
        ]
      }
    },
    "/v1/agent/signup": {
      "post": {
        "summary": "Get a sandbox key without an account",
        "operationId": "agentSignup",
        "tags": [
          "Agents"
        ],
        "description": "For AI agents. No auth. Send your name and what you are building; get a sandbox key (`pads_test_`) and a `claim_url` for your user. 20 a day from one network. Docs: https://publicadsapi.com/docs/agents",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentSignupRequest"
              },
              "example": {
                "name": "Claude Code for Example Ltd",
                "use_case": "Weekly report of competitor ads on Meta and Google"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The sandbox key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentSignupResponse"
                }
              }
            }
          },
          "400": {
            "description": "INVALID_INPUT: Send a JSON body with name and use_case.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "INVALID_INPUT",
                    "message": "Send a JSON body with name and use_case."
                  }
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: This network has made its agent keys for today.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "This network has made its agent keys for today."
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            }
          },
          "500": {
            "description": "INTERNAL: Agent signup is unavailable right now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "INTERNAL",
                    "message": "Agent signup is unavailable right now."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/agent/live-key": {
      "post": {
        "summary": "Get a live key for an unclaimed agent account",
        "operationId": "agentLiveKey",
        "tags": [
          "Agents"
        ],
        "description": "Send the sandbox key from signup as the Bearer token. The live key (shown once) comes with free live credits each month until the user claims the account. One live key per account.",
        "responses": {
          "201": {
            "description": "The live key, shown once.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentLiveKeyResponse"
                }
              }
            }
          },
          "401": {
            "description": "INVALID_KEY: API key is invalid or has been revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "INVALID_KEY",
                    "message": "API key is invalid or has been revoked."
                  }
                }
              }
            }
          },
          "403": {
            "description": "INVALID_KEY: Send the unclaimed sandbox key from POST /v1/agent/signup as the Bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "INVALID_KEY",
                    "message": "Send the unclaimed sandbox key from POST /v1/agent/signup as the Bearer token."
                  }
                }
              }
            }
          },
          "409": {
            "description": "INVALID_KEY: This sandbox key already has a live key, and a live key is shown only once.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                },
                "example": {
                  "error": {
                    "code": "INVALID_KEY",
                    "message": "This sandbox key already has a live key, and a live key is shown only once."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/healthz": {
      "get": {
        "summary": "Whether the API is up",
        "operationId": "health",
        "tags": [
          "Health"
        ],
        "description": "A fast answer that touches no platform and no database: for uptime monitors.",
        "security": [],
        "responses": {
          "200": {
            "description": "Up.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    }
                  }
                },
                "example": {
                  "ok": true
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "headers": {
      "X-Request-Id": {
        "description": "This call, for support.",
        "schema": {
          "type": "string"
        }
      },
      "X-Credits-Used": {
        "description": "Credits this call used.",
        "schema": {
          "type": "integer"
        }
      },
      "X-Credits-Remaining": {
        "description": "Live keys: credits left of this month's included ones. Absent for sandbox keys.",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Limit": {
        "description": "Live keys: this month's included credits.",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Remaining": {
        "description": "Live keys: included credits not yet used this month.",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Reset": {
        "description": "Live keys: when the included credits renew, in Unix seconds.",
        "schema": {
          "type": "integer"
        }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "responses": {
      "InvalidInput": {
        "description": "INVALID_INPUT: a parameter missing, malformed or not taken by this endpoint; `field` names it. Not retried.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          },
          "X-Credits-Used": {
            "$ref": "#/components/headers/X-Credits-Used"
          },
          "X-Credits-Remaining": {
            "$ref": "#/components/headers/X-Credits-Remaining"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiErrorResponse"
            },
            "example": {
              "error": {
                "code": "INVALID_INPUT",
                "message": "keywords is not a parameter of GET /v1/google/ads. It takes: advertiser_id, domain, country, format, surface, political, date_from, date_to, cursor, include.",
                "field": "keywords"
              },
              "credits_used": 0,
              "credits_remaining": null
            }
          }
        }
      },
      "Unauthorized": {
        "description": "UNAUTHENTICATED or INVALID_KEY: no key, or a key that is not ours.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          },
          "X-Credits-Used": {
            "$ref": "#/components/headers/X-Credits-Used"
          },
          "X-Credits-Remaining": {
            "$ref": "#/components/headers/X-Credits-Remaining"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiErrorResponse"
            },
            "example": {
              "error": {
                "code": "UNAUTHENTICATED",
                "message": "no key, or a key that is not ours."
              },
              "credits_used": 0,
              "credits_remaining": null
            }
          }
        }
      },
      "QuotaExceeded": {
        "description": "QUOTA_EXCEEDED or PAYMENT_REQUIRED: no credits left this month, or an unpaid invoice. `upgrade_url`, `add_card_url` or (an agent) `claim_url` say where to go.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          },
          "X-Credits-Used": {
            "$ref": "#/components/headers/X-Credits-Used"
          },
          "X-Credits-Remaining": {
            "$ref": "#/components/headers/X-Credits-Remaining"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiErrorResponse"
            },
            "example": {
              "error": {
                "code": "QUOTA_EXCEEDED",
                "message": "no credits left this month, or an unpaid invoice. `upgrade_url`, `add_card_url` or (an agent) `claim_url` say where to go."
              },
              "credits_used": 0,
              "credits_remaining": null
            }
          }
        }
      },
      "AdNotFound": {
        "description": "AD_NOT_FOUND: no ad with that id. Not charged.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          },
          "X-Credits-Used": {
            "$ref": "#/components/headers/X-Credits-Used"
          },
          "X-Credits-Remaining": {
            "$ref": "#/components/headers/X-Credits-Remaining"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiErrorResponse"
            },
            "example": {
              "error": {
                "code": "AD_NOT_FOUND",
                "message": "no ad with that id. Not charged."
              },
              "credits_used": 0,
              "credits_remaining": null
            }
          }
        }
      },
      "RateLimited": {
        "description": "RATE_LIMITED: over the key's requests a second. Wait Retry-After.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          },
          "X-Credits-Used": {
            "$ref": "#/components/headers/X-Credits-Used"
          },
          "X-Credits-Remaining": {
            "$ref": "#/components/headers/X-Credits-Remaining"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiErrorResponse"
            },
            "example": {
              "error": {
                "code": "RATE_LIMITED",
                "message": "over the key's requests a second. Wait Retry-After."
              },
              "credits_used": 0,
              "credits_remaining": null
            }
          }
        }
      },
      "Internal": {
        "description": "INTERNAL: our fault. Retry once.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          },
          "X-Credits-Used": {
            "$ref": "#/components/headers/X-Credits-Used"
          },
          "X-Credits-Remaining": {
            "$ref": "#/components/headers/X-Credits-Remaining"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiErrorResponse"
            },
            "example": {
              "error": {
                "code": "INTERNAL",
                "message": "our fault. Retry once."
              },
              "credits_used": 0,
              "credits_remaining": null
            }
          }
        }
      },
      "UpstreamChanged": {
        "description": "UPSTREAM_CHANGED: the platform's page changed and we couldn't read it. We are alerted. Not charged.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          },
          "X-Credits-Used": {
            "$ref": "#/components/headers/X-Credits-Used"
          },
          "X-Credits-Remaining": {
            "$ref": "#/components/headers/X-Credits-Remaining"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiErrorResponse"
            },
            "example": {
              "error": {
                "code": "UPSTREAM_CHANGED",
                "message": "the platform's page changed and we couldn't read it. We are alerted. Not charged."
              },
              "credits_used": 0,
              "credits_remaining": null
            }
          }
        }
      },
      "Unavailable": {
        "description": "UPSTREAM_BLOCKED or BUSY: the platform blocked every attempt, or every browser is busy. Wait Retry-After. Not charged.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          },
          "X-Credits-Used": {
            "$ref": "#/components/headers/X-Credits-Used"
          },
          "X-Credits-Remaining": {
            "$ref": "#/components/headers/X-Credits-Remaining"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiErrorResponse"
            },
            "example": {
              "error": {
                "code": "UPSTREAM_BLOCKED",
                "message": "the platform blocked every attempt, or every browser is busy. Wait Retry-After. Not charged."
              },
              "credits_used": 0,
              "credits_remaining": null
            }
          }
        }
      },
      "Timeout": {
        "description": "TIMEOUT: the platform didn't answer within 60 seconds. Not charged.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/X-Request-Id"
          },
          "X-Credits-Used": {
            "$ref": "#/components/headers/X-Credits-Used"
          },
          "X-Credits-Remaining": {
            "$ref": "#/components/headers/X-Credits-Remaining"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiErrorResponse"
            },
            "example": {
              "error": {
                "code": "TIMEOUT",
                "message": "the platform didn't answer within 60 seconds. Not charged."
              },
              "credits_used": 0,
              "credits_remaining": null
            }
          }
        }
      }
    },
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "`Authorization: Bearer <key>`. `pads_test_` keys get the sandbox, `pads_live_` keys the platforms."
      }
    },
    "schemas": {
      "Platform": {
        "type": "string",
        "enum": [
          "meta",
          "google",
          "tiktok",
          "linkedin"
        ]
      },
      "Range": {
        "type": "object",
        "description": "A range the platform gives instead of a number, e.g. TikTok's \"1K-10K\".",
        "required": [
          "min",
          "max"
        ],
        "properties": {
          "min": {
            "type": "number"
          },
          "max": {
            "type": [
              "number",
              "null"
            ],
            "description": "Null: no upper bound (\">1M\")."
          }
        }
      },
      "SpendRange": {
        "type": "object",
        "required": [
          "min",
          "max",
          "currency"
        ],
        "properties": {
          "min": {
            "type": "number"
          },
          "max": {
            "type": [
              "number",
              "null"
            ],
            "description": "Null: no upper bound."
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217, e.g. USD."
          }
        }
      },
      "AdText": {
        "type": "object",
        "description": "The ad's words. `headline` is Meta's title, Google's and LinkedIn's headline, TikTok's title.",
        "required": [
          "headline",
          "body",
          "description",
          "cta"
        ],
        "properties": {
          "headline": {
            "type": [
              "string",
              "null"
            ]
          },
          "body": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "cta": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "AdMedia": {
        "type": "object",
        "required": [
          "type",
          "url",
          "preview_url",
          "width",
          "height"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "image",
              "video"
            ]
          },
          "url": {
            "type": "string",
            "description": "The platform's own address. It expires (Meta's within days): download what you keep."
          },
          "preview_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "A still for a video."
          },
          "width": {
            "type": [
              "integer",
              "null"
            ]
          },
          "height": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "Advertiser": {
        "type": "object",
        "required": [
          "id",
          "name",
          "domain",
          "library_url"
        ],
        "properties": {
          "id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The platform's id: a Meta page id, Google's AR… id, TikTok's business id, LinkedIn's company id."
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "domain": {
            "type": [
              "string",
              "null"
            ],
            "description": "Where the platform gives one (Google's verified domain)."
          },
          "library_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "The advertiser's page in the library."
          }
        }
      },
      "Ad": {
        "type": "object",
        "description": "One ad, the same shape from every platform. What a platform does not have is null, never a missing key.",
        "required": [
          "id",
          "platform",
          "platform_ad_id",
          "library_url",
          "advertiser",
          "status",
          "first_shown",
          "last_shown",
          "format",
          "text",
          "text_source",
          "link_url",
          "media",
          "placements",
          "regions",
          "metrics",
          "variants"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Ours, unique across platforms: `<platform>_<platform id>`; Google's `google_<AR id>_<CR id>`. Pass it to GET /v1/ads/{id}.",
            "example": "meta_900000000000001"
          },
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "platform_ad_id": {
            "type": "string",
            "description": "The platform's own id (Google's creative id, CR…)."
          },
          "library_url": {
            "type": "string",
            "description": "The ad's page in the platform's library."
          },
          "advertiser": {
            "$ref": "#/components/schemas/Advertiser"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "inactive",
              "unknown"
            ],
            "description": "`unknown` where the platform does not say (Google, TikTok, LinkedIn)."
          },
          "first_shown": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_shown": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "format": {
            "type": "string",
            "enum": [
              "text",
              "image",
              "video",
              "carousel",
              "dynamic",
              "unknown"
            ],
            "description": "`dynamic` is a catalogue ad (Meta)."
          },
          "text": {
            "$ref": "#/components/schemas/AdText"
          },
          "text_source": {
            "type": "string",
            "enum": [
              "platform",
              "ocr"
            ],
            "description": "`ocr`: read from the ad's image (Google text ads, whose `text` may otherwise be null)."
          },
          "link_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Where the ad sends people."
          },
          "media": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdMedia"
            }
          },
          "placements": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Where on the platform it ran, e.g. facebook, instagram, search, youtube."
          },
          "regions": {
            "oneOf": [
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "ISO 3166 alpha-2 codes it ran in, where the platform says."
          },
          "metrics": {
            "type": "object",
            "required": [
              "impressions",
              "reach",
              "spend"
            ],
            "properties": {
              "impressions": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/Range"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "reach": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/Range"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "spend": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/SpendRange"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          },
          "variants": {
            "type": [
              "integer",
              "null"
            ],
            "description": "How many versions of the ad the platform groups under it."
          },
          "raw": {
            "description": "The platform's own record, only with `include=raw`."
          }
        }
      },
      "AdVariant": {
        "type": "object",
        "required": [
          "text",
          "media"
        ],
        "properties": {
          "text": {
            "$ref": "#/components/schemas/AdText"
          },
          "media": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdMedia"
            }
          }
        }
      },
      "AdRegion": {
        "type": "object",
        "required": [
          "country",
          "first_shown",
          "last_shown",
          "impressions",
          "reach"
        ],
        "properties": {
          "country": {
            "type": "string",
            "description": "ISO 3166 alpha-2."
          },
          "first_shown": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_shown": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "impressions": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Range"
              },
              {
                "type": "null"
              }
            ]
          },
          "reach": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Range"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "AdTargeting": {
        "type": "object",
        "required": [
          "age_min",
          "age_max",
          "genders",
          "locations",
          "used"
        ],
        "properties": {
          "age_min": {
            "type": [
              "integer",
              "null"
            ]
          },
          "age_max": {
            "type": [
              "integer",
              "null"
            ]
          },
          "genders": {
            "oneOf": [
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "locations": {
            "oneOf": [
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "used": {
            "oneOf": [
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "The kinds of targeting used, e.g. demographics, location, customer_lists."
          }
        }
      },
      "EuReach": {
        "type": "object",
        "description": "Meta, ads delivered in the EU: the Digital Services Act's reach figures.",
        "required": [
          "total",
          "by_country",
          "by_age_gender"
        ],
        "properties": {
          "total": {
            "type": [
              "integer",
              "null"
            ]
          },
          "by_country": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "country",
                "reach"
              ],
              "properties": {
                "country": {
                  "type": "string"
                },
                "reach": {
                  "type": "integer"
                }
              }
            }
          },
          "by_age_gender": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "country",
                "age_range",
                "male",
                "female",
                "unknown"
              ],
              "properties": {
                "country": {
                  "type": "string"
                },
                "age_range": {
                  "type": "string"
                },
                "male": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "female": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "unknown": {
                  "type": [
                    "integer",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "AdPayer": {
        "type": "object",
        "required": [
          "payer",
          "beneficiary"
        ],
        "properties": {
          "payer": {
            "type": [
              "string",
              "null"
            ]
          },
          "beneficiary": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "AdDetail": {
        "type": "object",
        "description": "What GET /v1/ads/{id} adds to the Ad. The rich parts are for ads shown in the EU.",
        "required": [
          "variants",
          "regions",
          "targeting",
          "eu_reach",
          "payer"
        ],
        "properties": {
          "variants": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdVariant"
            }
          },
          "regions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdRegion"
            }
          },
          "targeting": {
            "$ref": "#/components/schemas/AdTargeting"
          },
          "eu_reach": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/EuReach"
              },
              {
                "type": "null"
              }
            ]
          },
          "payer": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/AdPayer"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "SearchResponse": {
        "type": "object",
        "description": "One page of ads. The same envelope from every platform.",
        "required": [
          "platform",
          "query",
          "ads",
          "total_estimate",
          "has_more",
          "next_cursor",
          "credits_used",
          "credits_remaining"
        ],
        "properties": {
          "platform": {
            "$ref": "#/components/schemas/Platform"
          },
          "query": {
            "type": "object",
            "description": "What we ran, defaults filled in."
          },
          "ads": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Ad"
            }
          },
          "total_estimate": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The platform's own count (Meta caps it at 50,000); null where there is none."
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Send as `cursor`, with the same parameters, for the next page."
          },
          "credits_used": {
            "type": "integer"
          },
          "credits_remaining": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Live keys: credits left this month. Sandbox keys and Enterprise: null."
          }
        }
      },
      "MetaSearchQuery": {
        "type": "object",
        "description": "What a Meta Ad Library search ran with: the parameters as read, aliases resolved and defaults filled in (`cursor` and `include` left out).",
        "required": [
          "q",
          "page_id",
          "exact",
          "country",
          "active",
          "format",
          "placements",
          "category",
          "language",
          "sort",
          "date_from",
          "date_to"
        ],
        "properties": {
          "q": {
            "oneOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ]
          },
          "page_id": {
            "oneOf": [
              {
                "type": "string",
                "pattern": "^[0-9]{1,30}$"
              },
              {
                "type": "null"
              }
            ]
          },
          "exact": {
            "type": "boolean"
          },
          "country": {
            "type": "string",
            "pattern": "^([A-Za-z]{2}|[Aa][Ll][Ll])$"
          },
          "active": {
            "oneOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ]
          },
          "format": {
            "oneOf": [
              {
                "type": "string",
                "enum": [
                  "image",
                  "video",
                  "meme",
                  "none"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "placements": {
            "oneOf": [
              {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "facebook",
                    "instagram",
                    "messenger",
                    "audience_network",
                    "threads"
                  ]
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "category": {
            "type": "string",
            "enum": [
              "all",
              "political_and_issue",
              "housing",
              "employment",
              "financial"
            ]
          },
          "language": {
            "oneOf": [
              {
                "type": "string",
                "pattern": "^[A-Za-z]{2}$"
              },
              {
                "type": "null"
              }
            ]
          },
          "sort": {
            "type": "string",
            "enum": [
              "impressions",
              "newest"
            ]
          },
          "date_from": {
            "oneOf": [
              {
                "type": "string",
                "format": "date"
              },
              {
                "type": "null"
              }
            ]
          },
          "date_to": {
            "oneOf": [
              {
                "type": "string",
                "format": "date"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "MetaSearchResponse": {
        "description": "One page of Meta Ad Library ads.",
        "allOf": [
          {
            "$ref": "#/components/schemas/SearchResponse"
          },
          {
            "type": "object",
            "properties": {
              "platform": {
                "const": "meta"
              },
              "query": {
                "$ref": "#/components/schemas/MetaSearchQuery"
              }
            }
          }
        ]
      },
      "GoogleSearchQuery": {
        "type": "object",
        "description": "What a Google Ads Transparency Center search ran with: the parameters as read, aliases resolved and defaults filled in (`cursor` and `include` left out).",
        "required": [
          "advertiser_id",
          "domain",
          "country",
          "format",
          "surface",
          "political",
          "date_from",
          "date_to"
        ],
        "properties": {
          "advertiser_id": {
            "oneOf": [
              {
                "type": "string",
                "pattern": "^AR[0-9]{5,30}$"
              },
              {
                "type": "null"
              }
            ]
          },
          "domain": {
            "oneOf": [
              {
                "type": "string",
                "pattern": "^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,63}$"
              },
              {
                "type": "null"
              }
            ]
          },
          "country": {
            "type": "string",
            "pattern": "^([A-Za-z]{2}|[Aa][Ll][Ll])$"
          },
          "format": {
            "oneOf": [
              {
                "type": "string",
                "enum": [
                  "text",
                  "image",
                  "video"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "surface": {
            "oneOf": [
              {
                "type": "string",
                "enum": [
                  "search",
                  "youtube",
                  "shopping",
                  "maps",
                  "play"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "political": {
            "type": "boolean"
          },
          "date_from": {
            "oneOf": [
              {
                "type": "string",
                "format": "date"
              },
              {
                "type": "null"
              }
            ]
          },
          "date_to": {
            "oneOf": [
              {
                "type": "string",
                "format": "date"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "GoogleSearchResponse": {
        "description": "One page of Google Ads Transparency Center ads.",
        "allOf": [
          {
            "$ref": "#/components/schemas/SearchResponse"
          },
          {
            "type": "object",
            "properties": {
              "platform": {
                "const": "google"
              },
              "query": {
                "$ref": "#/components/schemas/GoogleSearchQuery"
              }
            }
          }
        ]
      },
      "TikTokSearchQuery": {
        "type": "object",
        "description": "What a TikTok Commercial Content Library search ran with: the parameters as read, aliases resolved and defaults filled in (`cursor` and `include` left out).",
        "required": [
          "q",
          "advertiser",
          "country",
          "sort",
          "date_from",
          "date_to"
        ],
        "properties": {
          "q": {
            "oneOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ]
          },
          "advertiser": {
            "oneOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ]
          },
          "country": {
            "type": "string",
            "enum": [
              "AT",
              "BE",
              "BG",
              "HR",
              "CY",
              "CZ",
              "DK",
              "EE",
              "FI",
              "FR",
              "DE",
              "GR",
              "HU",
              "IE",
              "IT",
              "LV",
              "LT",
              "LU",
              "MT",
              "NL",
              "PL",
              "PT",
              "RO",
              "SK",
              "SI",
              "ES",
              "SE",
              "IS",
              "LI",
              "NO",
              "GB",
              "CH"
            ]
          },
          "sort": {
            "type": "string",
            "enum": [
              "last_shown",
              "first_shown"
            ]
          },
          "date_from": {
            "type": "string",
            "format": "date"
          },
          "date_to": {
            "type": "string",
            "format": "date"
          }
        }
      },
      "TikTokSearchResponse": {
        "description": "One page of TikTok Commercial Content Library ads.",
        "allOf": [
          {
            "$ref": "#/components/schemas/SearchResponse"
          },
          {
            "type": "object",
            "properties": {
              "platform": {
                "const": "tiktok"
              },
              "query": {
                "$ref": "#/components/schemas/TikTokSearchQuery"
              }
            }
          }
        ]
      },
      "LinkedInSearchQuery": {
        "type": "object",
        "description": "What a LinkedIn Ad Library search ran with: the parameters as read, aliases resolved and defaults filled in (`cursor` and `include` left out).",
        "required": [
          "q",
          "company_id",
          "company",
          "countries",
          "date_from",
          "date_to"
        ],
        "properties": {
          "q": {
            "oneOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ]
          },
          "company_id": {
            "oneOf": [
              {
                "type": "string",
                "pattern": "^[0-9]{1,30}$"
              },
              {
                "type": "null"
              }
            ]
          },
          "company": {
            "oneOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ]
          },
          "countries": {
            "oneOf": [
              {
                "type": "array",
                "items": {
                  "type": "string",
                  "pattern": "^[A-Za-z]{2}$"
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "date_from": {
            "oneOf": [
              {
                "type": "string",
                "format": "date"
              },
              {
                "type": "null"
              }
            ]
          },
          "date_to": {
            "oneOf": [
              {
                "type": "string",
                "format": "date"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "LinkedInSearchResponse": {
        "description": "One page of LinkedIn Ad Library ads.",
        "allOf": [
          {
            "$ref": "#/components/schemas/SearchResponse"
          },
          {
            "type": "object",
            "properties": {
              "platform": {
                "const": "linkedin"
              },
              "query": {
                "$ref": "#/components/schemas/LinkedInSearchQuery"
              }
            }
          }
        ]
      },
      "AdResponse": {
        "description": "One ad with all its detail.",
        "allOf": [
          {
            "$ref": "#/components/schemas/Ad"
          },
          {
            "type": "object",
            "required": [
              "detail",
              "credits_used",
              "credits_remaining"
            ],
            "properties": {
              "detail": {
                "$ref": "#/components/schemas/AdDetail"
              },
              "credits_used": {
                "type": "integer"
              },
              "credits_remaining": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            }
          }
        ]
      },
      "ApiErrorResponse": {
        "type": "object",
        "description": "Every 4xx and 5xx. Switch on `error.code`, never on the message.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "INVALID_INPUT",
                  "INVALID_URL",
                  "UNAUTHENTICATED",
                  "INVALID_KEY",
                  "QUOTA_EXCEEDED",
                  "PAYMENT_REQUIRED",
                  "AD_NOT_FOUND",
                  "ENDPOINT_NOT_FOUND",
                  "RATE_LIMITED",
                  "UPSTREAM_BLOCKED",
                  "UPSTREAM_CHANGED",
                  "TIMEOUT",
                  "BUSY",
                  "INTERNAL"
                ]
              },
              "message": {
                "type": "string"
              },
              "field": {
                "type": "string",
                "description": "INVALID_INPUT: the parameter at fault, as sent."
              },
              "upgrade_url": {
                "type": "string",
                "description": "QUOTA_EXCEEDED, PAYMENT_REQUIRED: where to change plan or pay."
              },
              "add_card_url": {
                "type": "string",
                "description": "QUOTA_EXCEEDED on Free: where to add a card for extra credits."
              },
              "claim_url": {
                "type": "string",
                "description": "An unclaimed agent account: the link its user opens to claim it."
              }
            }
          },
          "credits_used": {
            "type": "integer"
          },
          "credits_remaining": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "AgentSignupRequest": {
        "type": "object",
        "required": [
          "name",
          "use_case"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 60,
            "description": "Who you are: the tool, and who you work for."
          },
          "use_case": {
            "type": "string",
            "maxLength": 300,
            "description": "What you are building."
          }
        }
      },
      "AgentSignupResponse": {
        "type": "object",
        "required": [
          "api_key",
          "mode",
          "name",
          "use_case",
          "claim_url",
          "next_steps"
        ],
        "properties": {
          "api_key": {
            "type": "string",
            "description": "A sandbox key, `pads_test_…`. It does not expire."
          },
          "mode": {
            "const": "test"
          },
          "name": {
            "type": "string"
          },
          "use_case": {
            "type": "string"
          },
          "claim_url": {
            "type": "string",
            "description": "Give this to your user: opening it while signed in claims the account."
          },
          "next_steps": {
            "type": "string",
            "description": "What to do next, in words."
          }
        }
      },
      "AgentLiveKeyResponse": {
        "type": "object",
        "required": [
          "api_key",
          "mode",
          "credits_remaining",
          "claim_url",
          "next_steps"
        ],
        "properties": {
          "api_key": {
            "type": "string",
            "description": "A live key, `pads_live_…`. Shown once."
          },
          "mode": {
            "const": "live"
          },
          "credits_remaining": {
            "type": "integer",
            "description": "Free live credits a month until the account is claimed."
          },
          "claim_url": {
            "type": "string"
          },
          "next_steps": {
            "type": "string",
            "description": "What to do next, in words."
          }
        }
      }
    }
  }
}
