{
 "openapi": "3.1.0",
 "info": {
  "title": "Poseidon API",
  "version": "1.2.0",
  "summary": "Winning e-commerce ads from the Meta Ad Library (EU/UK), measured facts only.",
  "description": "Ask \"give me a winning format, go look\" and get real ads back: the Ad Library link, days live, EU reach by country and age x gender, versions, the shop behind the ad, and the verbatim script cut into sections.\n\n**The promise.** Every number carries the day it was read and its source. A value Poseidon doesn't have is absent from the answer, never filled in. Aggregates (weights, a brand's median days live) are not returned below the minimum sample of 30 ads. Links only, never media. No search by person name.\n\n**Coverage.** Ads delivered in the EU or UK only. The only number of people shown is Meta's EU total reach (eu_reach, \"EU reach (Meta's total)\"), as read on the date shown. Meta publishes no spend or impressions for commercial ads. Countries, ages and genders are shown only as shares of Meta's age x gender breakdown of the same read, never as numbers of people: Meta's breakdown rows do not add up to its EU total (measured 0.93x to 1.10x on 3,820 reads), so a country figure in people would not be a fact.\n\n**Winner rule.** A winner is an ad live for at least 21 days: days between Meta's start date and its stop date, or our last read if it is still active. Override with min_days_live.\n\n**Credits.** 1 credit = €0.01, prepaid, never expire. search_winners 5; search_experiments 5; get_ad 2; get_brand 5; get_weights 10; filters 0; account 0. A call that returns nothing costs 0. Errors and refusals are never billed.\n\n**Rate limits (they protect the server, not your wallet).** 3 requests a second per key, bursts of 15; 8 s at most per query; 50 rows per page, offset up to 1000. A 429 answer has a Retry-After header.\n\n**Versions.** Paths are versioned (/v1). Inside v1, changes only add things. See CHANGELOG.md."
 },
 "servers": [
  {
   "url": "http://127.0.0.1:8787",
   "description": "Today: through an SSH tunnel (ssh -N -L 8787:127.0.0.1:8787 <server>)"
  },
  {
   "url": "https://api.askposeidon.app",
   "description": "Public address"
  }
 ],
 "security": [
  {
   "bearer": []
  }
 ],
 "paths": {
  "/v1/filters": {
   "get": {
    "operationId": "filters",
    "summary": "What you can filter on, with live counts",
    "description": "Free. What Poseidon can filter on, with live counts: niches, Meta display formats, countries with reach data, age ranges, hook types (none measured yet), sort orders, the winner rule and the coverage note. Call it first.\n\nCost: free. MCP tool: `filters`.",
    "x-credits": 0,
    "x-example-request": "/v1/filters",
    "parameters": [],
    "responses": {
     "200": {
      "description": "The answer, with `meta` (credits charged and left, coverage, notes).",
      "headers": {
       "Poseidon-API-Version": {
        "schema": {
         "type": "string"
        }
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Envelope"
        },
        "example": {
         "data": {
          "niche": {
           "values": [
            {
             "v": "weight loss",
             "brands": 440,
             "ads": 12408
            },
            {
             "v": "cellulite",
             "brands": 174,
             "ads": 4823
            }
           ],
           "note": "niche = the niche of the keyword search that found the brand on the Ad Library. It is a fact about our search, not a judgment of the brand: a brand found by 'weight loss' may sell something else."
          },
          "format": {
           "values": [
            {
             "v": "VIDEO",
             "ads": 16919
            },
            {
             "v": "IMAGE",
             "ads": 12457
            }
           ],
           "note": "Meta's display format as shown on the Ad Library. Creative types such as UGC, VSL or podcast are not labelled on any ad yet, so they cannot be filtered."
          },
          "country": {
           "values": [
            {
             "v": "BE",
             "ads_with_reach": 939
            },
            {
             "v": "FR",
             "ads_with_reach": 883
            }
           ],
           "note": "An ad is in a country when Meta's reach breakdown shows people reached there. Ads whose reach was never read have no country."
          },
          "age": {
           "values": [
            "13-17",
            "18-24"
           ]
          },
          "gender": {
           "values": [
            "female",
            "male"
           ]
          },
          "hook_type": {
           "values": [],
           "note": "Hook types are not measured on any ad yet: this filter returns nothing."
          },
          "sort": {
           "values": [
            "country_share",
            "days_live"
           ]
          },
          "reach_reads": {
           "first": "2026-11-11",
           "last": "2026-11-12",
           "ads": 3661
          },
          "winner_rule": "A winner is an ad live for at least 21 days: days between Meta's start date and its stop date, or our last read if it is still active. Override with min_days_live.",
          "coverage": "Ads delivered in the EU or UK only. The only number of people shown is Meta's EU total reach (eu_reach, \"EU reach (Meta's total)\"), as read on the date shown. Meta publishes no spend or impressions for commercial ads. Countries, ages and genders are shown only as shares of Meta's age x gender breakdown of the same read, never as numbers of people: Meta's breakdown rows do not add up to its EU total (measured 0.93x to 1.10x on 3,820 reads), so a country figure in people would not be a fact."
         },
         "meta": {
          "endpoint": "filters",
          "api_version": "1.1.0",
          "credits_charged": 0,
          "credits_left": 500,
          "answered_at": "2026-11-12T22:11:37+00:00",
          "coverage": "Ads delivered in the EU or UK only. The only number of people shown is Meta's EU total reach (eu_reach, \"EU reach (Meta's total)\"), as read on the date shown. Meta publishes no spend or impressions for commercial ads. Countries, ages and genders are shown only as shares of Meta's age x gender breakdown of the same read, never as numbers of people: Meta's breakdown rows do not add up to its EU total (measured 0.93x to 1.10x on 3,820 reads), so a country figure in people would not be a fact."
         }
        }
       }
      }
     },
     "401": {
      "description": "Missing, unknown or revoked API key.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "missing or invalid API key"
        }
       }
      }
     },
     "429": {
      "description": "Too many requests for this key. Wait Retry-After seconds. Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "too many requests for this key (not billed)",
         "retry_after_s": 0.27
        }
       }
      }
     },
     "503": {
      "description": "Server busy or a query took too long. Retry. Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/v1/winners": {
   "get": {
    "operationId": "search_winners",
    "summary": "Search winning ads",
    "description": "Find winning e-commerce ads from the Meta Ad Library (EU/UK delivered ads, confirmed e-commerce shops only). Winner = live 21+ days by default. Each result: the ad's link on the Ad Library, the brand and its shop link, format, days live, versions (Meta's count of ads sharing the creative), active or stopped, EU reach (Meta's total, the only number of people) with the day it was read, the share of Meta's age x gender breakdown in the country / segment you filtered on (a share, never people), and the verbatim hook when its script was cut into sections. A field we don't have is absent, never guessed. No media and no person-name search. Costs 5 credits per search (0 if nothing matches).\n\nCost: 5 credits (€0.05); 0 if nothing is returned. MCP tool: `search_winners`.",
    "x-credits": 5,
    "x-example-request": "/v1/winners?niche=weight%20loss&country=FR&format=VIDEO&has_script=true&limit=2",
    "parameters": [
     {
      "name": "niche",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "niche of the keyword search that found the brand (see filters)"
     },
     {
      "name": "country",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "2-letter code: ads with Meta reach in that country, e.g. FR"
     },
     {
      "name": "format",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "Meta's display format: VIDEO, IMAGE, DCO, DPA, CAROUSEL… (creative types like UGC are not labelled yet)"
     },
     {
      "name": "hook_type",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "not measured yet: returns nothing"
     },
     {
      "name": "age",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string",
       "enum": [
        "13-17",
        "18-24",
        "25-34",
        "35-44",
        "45-54",
        "55-64",
        "65+"
       ]
      }
     },
     {
      "name": "gender",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string",
       "enum": [
        "female",
        "male"
       ]
      }
     },
     {
      "name": "active",
      "in": "query",
      "required": false,
      "schema": {
       "type": "boolean"
      },
      "description": "true = still running at our last read, false = stopped"
     },
     {
      "name": "min_days_live",
      "in": "query",
      "required": false,
      "schema": {
       "type": "integer",
       "minimum": 0
      },
      "description": "default 21"
     },
     {
      "name": "max_days_live",
      "in": "query",
      "required": false,
      "schema": {
       "type": "integer",
       "minimum": 0
      }
     },
     {
      "name": "min_eu_reach",
      "in": "query",
      "required": false,
      "schema": {
       "type": "integer",
       "minimum": 0
      }
     },
     {
      "name": "min_country_share",
      "in": "query",
      "required": false,
      "schema": {
       "type": "number",
       "minimum": 0,
       "maximum": 1
      },
      "description": "needs country: the country's minimum share of Meta's breakdown, 0 to 1"
     },
     {
      "name": "min_segment_share",
      "in": "query",
      "required": false,
      "schema": {
       "type": "number",
       "minimum": 0,
       "maximum": 1
      },
      "description": "needs age or gender: the segment's minimum share of Meta's breakdown"
     },
     {
      "name": "min_versions",
      "in": "query",
      "required": false,
      "schema": {
       "type": "integer",
       "minimum": 0
      }
     },
     {
      "name": "has_script",
      "in": "query",
      "required": false,
      "schema": {
       "type": "boolean"
      },
      "description": "true = only ads whose script is cut into sections"
     },
     {
      "name": "started_after",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string",
       "format": "date"
      }
     },
     {
      "name": "started_before",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string",
       "format": "date"
      }
     },
     {
      "name": "sort",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string",
       "enum": [
        "country_share",
        "days_live",
        "eu_reach",
        "newest",
        "segment_share",
        "versions"
       ]
      },
      "description": "default: eu_reach if a country, age or gender is given, else days_live"
     },
     {
      "name": "limit",
      "in": "query",
      "required": false,
      "schema": {
       "type": "integer",
       "minimum": 1,
       "maximum": 50
      },
      "description": "default 20"
     },
     {
      "name": "offset",
      "in": "query",
      "required": false,
      "schema": {
       "type": "integer",
       "minimum": 0,
       "maximum": 1000
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The answer, with `meta` (credits charged and left, coverage, notes).",
      "headers": {
       "Poseidon-API-Version": {
        "schema": {
         "type": "string"
        }
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Envelope"
        },
        "example": {
         "data": {
          "filters_used": {
           "niche": "weight loss",
           "format": "VIDEO",
           "country": "FR",
           "has_script": true,
           "min_days_live": 21,
           "sort": "eu_reach",
           "limit": 2,
           "offset": 0
          },
          "matching_ads": 6,
          "matching_ads_with_script": 6,
          "results": [
           {
            "ad_id": "862034243491035",
            "library_url": "https://www.facebook.com/ads/library/?id=862034243491035",
            "format": "VIDEO",
            "active": true,
            "status_read_on": "2026-11-12",
            "source": "Meta Ad Library (ad fields: dates, status, format, versions, landing link)",
            "start_date": "2026-03-08",
            "days_live": 249,
            "versions": 1,
            "landing_url": "https://epycure.com/products/boost-metabolisme",
            "brand": {
             "page_id": "2218331548432159",
             "name": "Epycure",
             "shop_domain": "epycure.com",
             "shop_url": "https://epycure.com",
             "shop_platform": "shopify",
             "niche_of_search": "weight loss"
            },
            "eu_reach": {
             "total": 2766753,
             "label": "EU reach (Meta's total)",
             "read_on": "2026-11-12",
             "source": "Meta Ad Library (public ad page, read by Poseidon)",
             "country": {
              "code": "FR",
              "share_of_meta_breakdown": 1.0
             }
            },
            "hook": {
             "text": "J'ai perdu 10 kilos en 3 mois, comment t'expliques que ça marche aussi bien ?",
             "labelled_by": "AI section labelling (Claude Fable)"
            },
            "has_script": true
           },
           {
            "ad_id": "2112259736053955",
            "library_url": "https://www.facebook.com/ads/library/?id=2112259736053955",
            "format": "VIDEO",
            "active": true,
            "status_read_on": "2026-11-12",
            "source": "Meta Ad Library (ad fields: dates, status, format, versions, landing link)",
            "start_date": "2026-09-11",
            "days_live": 62,
            "versions": 2,
            "landing_url": "https://emulse.com/pages/5-raisons-listicles-immunite",
            "brand": {
             "page_id": "110514998585916",
             "name": "Emulse",
             "shop_domain": "emulse.com",
             "shop_url": "https://emulse.com",
             "shop_platform": "shopify",
             "niche_of_search": "weight loss"
            },
            "eu_reach": {
             "total": 1736020,
             "label": "EU reach (Meta's total)",
             "read_on": "2026-11-12",
             "source": "Meta Ad Library (public ad page, read by Poseidon)",
             "country": {
              "code": "FR",
              "share_of_meta_breakdown": 0.8786
             }
            },
            "hook": {
             "text": "Non, mes pâtes n'ont pas bon goût. Si je les lèche, c'est parce que ça brûle.",
             "labelled_by": "AI section labelling (Claude Fable)"
            },
            "has_script": true
           }
          ]
         },
         "meta": {
          "endpoint": "search_winners",
          "api_version": "1.1.0",
          "credits_charged": 5,
          "credits_left": 527,
          "answered_at": "2026-11-12T22:11:37+00:00",
          "coverage": "Ads delivered in the EU or UK only. The only number of people shown is Meta's EU total reach (eu_reach, \"EU reach (Meta's total)\"), as read on the date shown. Meta publishes no spend or impressions for commercial ads. Countries, ages and genders are shown only as shares of Meta's age x gender breakdown of the same read, never as numbers of people: Meta's breakdown rows do not add up to its EU total (measured 0.93x to 1.10x on 3,820 reads), so a country figure in people would not be a fact.",
          "notes": [
           "A winner is an ad live for at least 21 days: days between Meta's start date and its stop date, or our last read if it is still active. Override with min_days_live.",
           "niche = the niche of the keyword search that found the brand on the Ad Library. It is a fact about our search, not a judgment of the brand: a brand found by 'weight loss' may sell something else."
          ]
         }
        }
       }
      }
     },
     "400": {
      "description": "A parameter is refused (the answer lists the accepted values). Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "unknown format 'UGC'",
         "formats": [
          "CAROUSEL",
          "DCO"
         ],
         "note": "format is Meta's display format. Creative types such as UGC, VSL or podcast are not labelled on any ad yet. Use VIDEO for video ads."
        }
       }
      }
     },
     "401": {
      "description": "Missing, unknown or revoked API key.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "missing or invalid API key"
        }
       }
      }
     },
     "402": {
      "description": "Not enough credits for this call. Credits never expire; top up with a pack.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "not enough credits for an ad: 2 needed, 0 left. Credits never expire; top up to continue.",
         "credits_left": 0
        }
       }
      }
     },
     "429": {
      "description": "Too many requests for this key. Wait Retry-After seconds. Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "too many requests for this key (not billed)",
         "retry_after_s": 0.27
        }
       }
      }
     },
     "503": {
      "description": "Server busy or a query took too long. Retry. Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/v1/ads/{ad_id}": {
   "get": {
    "operationId": "get_ad",
    "summary": "One ad in full",
    "description": "One ad in full: Ad Library link, brand and shop, ad copy, format, dates, versions, EU reach history (Meta's total per read day; countries and age x gender as shares of Meta's breakdown), the verbatim script sentence by sentence with each sentence's section (Hook, Problem, Proof, Offer, CTA…, and who labelled it), change history and the source of every field. Costs 2 credits.\n\nCost: 2 credits (€0.02); 0 if nothing is returned. MCP tool: `get_ad`.",
    "x-credits": 2,
    "x-example-request": "/v1/ads/862034243491035",
    "parameters": [
     {
      "name": "ad_id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "Meta Ad Library id"
     }
    ],
    "responses": {
     "200": {
      "description": "The answer, with `meta` (credits charged and left, coverage, notes).",
      "headers": {
       "Poseidon-API-Version": {
        "schema": {
         "type": "string"
        }
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Envelope"
        },
        "example": {
         "data": {
          "ad_id": "862034243491035",
          "library_url": "https://www.facebook.com/ads/library/?id=862034243491035",
          "brand": {
           "page_id": "2218331548432159",
           "name": "Epycure",
           "shop_domain": "epycure.com",
           "shop_url": "https://epycure.com",
           "shop_platform": "shopify",
           "niche_of_search": "weight loss"
          },
          "brand_shop_check": "confirmed e-commerce shop",
          "format": "VIDEO",
          "platforms": [
           "FACEBOOK",
           "INSTAGRAM"
          ],
          "active": true,
          "status_read_on": "2026-11-12",
          "first_seen_on": "2026-11-11",
          "start_date": "2026-03-08",
          "days_live": 249,
          "versions": 1,
          "in_eu": true,
          "landing_url": "https://epycure.com/products/boost-metabolisme",
          "ad_copy": {
           "body": "Imaginez-vous retrouver confiance en vous, prendre plaisir à manger équilibré et bouger votre corps pour prendre soin de votre santé, pas pour compenser vos écarts.\n\nLe Boost métabolisme a été spécialement formulé avec du Morosil® pour vous accompagner dans votre remise en forme en relançant votre métabolisme ralenti à cause des régimes restrictifs, en déstockant les graisses accumulées et en limitant les fringales pour une perte de poids saine et durable.\nLes résultats des études cliniques* parlent d’eux-mêmes :\n＋ Perte moyenne de 3 kg\n＋ Diminution de 7 cm de tour de taille\n＋ Diminution de 6 cm de tour de cuisse\n______\nMorosil® est une marque déposée de Bionap srl.\n*Efficacité prouvée dans 2 études cliniques menées en double aveugle avec placebo sur 60 volontaires (IMC compris entre 25 et 35 kg/m²) pendant 12 semaines, à la dose de 400 mg par jour.”",
           "title": "Jusqu'à -7kg en 3 mois",
           "cta": "Acheter"
          },
          "eu_reach": {
           "label": "EU reach (Meta's total)",
           "history": [
            {
             "day": "2026-11-12",
             "total": 2766753,
             "source": "Meta Ad Library (public ad page, read by Poseidon)"
            }
           ],
           "targeting_latest": {
            "age_min": 18,
            "age_max": 65,
            "gender": "Women"
           },
           "country_share_history": {
            "FR": [
             {
              "day": "2026-11-12",
              "share_of_meta_breakdown": 1.0
             }
            ]
           },
           "age_gender_share_latest": {
            "day": "2026-11-12",
            "note": "shares of Meta's age x gender breakdown of this read (all cells add up to 1), not numbers of people",
            "rows": {
             "FR": [
              {
               "age": "18-24",
               "male_share": 0.0064,
               "female_share": 0.1476,
               "unknown_share": 0.0015
              },
              {
               "age": "25-34",
               "male_share": 0.0231,
               "female_share": 0.4202,
               "unknown_share": 0.003
              }
             ]
            }
           }
          },
          "script": {
           "media_type": "video",
           "duration_s": 64.74538,
           "language": "fr",
           "transcribed_by": "speech-to-text (Whisper)",
           "media_read_from": "Meta Ad Library (public ad page, read by Poseidon)",
           "transcribed_on": "2026-11-12",
           "sentences": [
            {
             "kind": "speech",
             "n": 1,
             "start_ms": 0,
             "end_ms": 3280,
             "text": "J'ai perdu 10 kilos en 3 mois, comment t'expliques que ça marche aussi bien ?",
             "section": "Hook",
             "labelled_by": "AI section labelling (Claude Fable)"
            },
            {
             "kind": "speech",
             "n": 2,
             "start_ms": 3280,
             "end_ms": 6040,
             "text": "Mais en fait, on ne vend pas de rêve, on vend de la science.",
             "section": "Proof",
             "labelled_by": "AI section labelling (Claude Fable)"
            },
            {
             "kind": "speech",
             "n": 3,
             "start_ms": 6040,
             "end_ms": 12280,
             "text": "Et c'est justement la force qu'on a chez Epicure Laboratoire, c'est de s'attacher à chaque fois à sélectionner des actifs qui ont fait l'objet d'études cliniques.",
             "section": "Proof",
             "labelled_by": "AI section labelling (Claude Fable)"
            },
            {
             "kind": "speech",
             "n": 4,
             "start_ms": 12280,
             "end_ms": 14480,
             "text": "Donc là, par exemple, tu me parles du boost métabolisme.",
             "section": "Product",
             "labelled_by": "AI section labelling (Claude Fable)"
            },
            {
             "kind": "speech",
             "n": 5,
             "start_ms": 14480,
             "end_ms": 16480,
             "text": "Il contient un actif phare qui est le morosil.",
             "section": "Product",
             "labelled_by": "AI section labelling (Claude Fable)"
            },
            {
             "kind": "speech",
             "n": 6,
             "start_ms": 16480,
             "end_ms": 25200,
             "text": "Et c'est ce morosil qui a fait l'objet de plusieurs études cliniques qui ont prouvé l'efficacité de cet actif sur le déstockage de graisse et en particulier au niveau du ventre et des hanches.",
             "section": "Proof",
             "labelled_by": "AI section labelling (Claude Fable)"
            }
           ]
          },
          "sources": [
           {
            "source": "Meta Ad Library (public ad page, read by Poseidon)",
            "read_on": "2026-11-12",
            "fields": [
             "body",
             "countries"
            ]
           }
          ]
         },
         "meta": {
          "endpoint": "get_ad",
          "api_version": "1.1.0",
          "credits_charged": 2,
          "credits_left": 525,
          "answered_at": "2026-11-12T22:11:37+00:00",
          "coverage": "Ads delivered in the EU or UK only. The only number of people shown is Meta's EU total reach (eu_reach, \"EU reach (Meta's total)\"), as read on the date shown. Meta publishes no spend or impressions for commercial ads. Countries, ages and genders are shown only as shares of Meta's age x gender breakdown of the same read, never as numbers of people: Meta's breakdown rows do not add up to its EU total (measured 0.93x to 1.10x on 3,820 reads), so a country figure in people would not be a fact."
         }
        }
       }
      }
     },
     "400": {
      "description": "A parameter is refused (the answer lists the accepted values). Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "unknown format 'UGC'",
         "formats": [
          "CAROUSEL",
          "DCO"
         ],
         "note": "format is Meta's display format. Creative types such as UGC, VSL or podcast are not labelled on any ad yet. Use VIDEO for video ads."
        }
       }
      }
     },
     "401": {
      "description": "Missing, unknown or revoked API key.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "missing or invalid API key"
        }
       }
      }
     },
     "402": {
      "description": "Not enough credits for this call. Credits never expire; top up with a pack.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "not enough credits for an ad: 2 needed, 0 left. Credits never expire; top up to continue.",
         "credits_left": 0
        }
       }
      }
     },
     "404": {
      "description": "Not in Poseidon.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "ad 1234567890 is not in Poseidon"
        }
       }
      }
     },
     "429": {
      "description": "Too many requests for this key. Wait Retry-After seconds. Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "too many requests for this key (not billed)",
         "retry_after_s": 0.27
        }
       }
      }
     },
     "503": {
      "description": "Server busy or a query took too long. Retry. Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/v1/brands/{page_id}": {
   "get": {
    "operationId": "get_brand",
    "summary": "A brand by its Meta page id",
    "description": "A brand: its Ad Library page link, shop (link, platform), how it was found, its ads in Poseidon (counts by format, winners, median days live, ads with reach) and its 10 longest-running ads. Give page_id or the shop domain. Costs 5 credits.\n\nCost: 5 credits (€0.05); 0 if nothing is returned. MCP tool: `get_brand`.",
    "x-credits": 5,
    "x-example-request": "/v1/brands/2218331548432159",
    "parameters": [
     {
      "name": "page_id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The answer, with `meta` (credits charged and left, coverage, notes).",
      "headers": {
       "Poseidon-API-Version": {
        "schema": {
         "type": "string"
        }
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Envelope"
        },
        "example": {
         "data": {
          "brands": [
           {
            "page_id": "2218331548432159",
            "name": "Epycure",
            "library_url": "https://www.facebook.com/ads/library/?view_all_page_id=2218331548432159",
            "niche_of_search": "weight loss",
            "found_by": "mot-clé : perte de poids",
            "first_added_on": "2026-11-11",
            "shop_check": "confirmed e-commerce shop",
            "shop_checked_on": "2026-11-12",
            "active_ads_on_meta_page": {
             "count": 420,
             "read_on": "2026-11-12"
            },
            "shop": {
             "domain": "epycure.com",
             "url": "https://epycure.com",
             "platform": "shopify",
             "checked_on": "2026-11-12",
             "check_method": "the shop's own website (its /products.json, then platform markers in its pages)"
            },
            "ads_in_poseidon": {
             "ads": 30,
             "active": 30,
             "winners_21_days": 28,
             "median_days_live": 108.5,
             "last_read": "2026-11-12",
             "ads_by_format": [
              {
               "format": "VIDEO",
               "ads": 21
              },
              {
               "format": "DCO",
               "ads": 5
              }
             ],
             "ads_with_eu_reach": 25,
             "last_reach_read": "2026-11-12",
             "computed_by": "Poseidon, from the ads above, at answer time"
            },
            "longest_running_ads": [
             {
              "ad_id": "862034243491035",
              "library_url": "https://www.facebook.com/ads/library/?id=862034243491035",
              "format": "VIDEO",
              "active": true,
              "status_read_on": "2026-11-12",
              "source": "Meta Ad Library (ad fields: dates, status, format, versions, landing link)",
              "start_date": "2026-03-08",
              "days_live": 249,
              "versions": 1,
              "landing_url": "https://epycure.com/products/boost-metabolisme",
              "eu_reach": {
               "total": 2766753,
               "label": "EU reach (Meta's total)",
               "read_on": "2026-11-12",
               "source": "Meta Ad Library (public ad page, read by Poseidon)"
              },
              "hook": {
               "text": "J'ai perdu 10 kilos en 3 mois, comment t'expliques que ça marche aussi bien ?",
               "labelled_by": "AI section labelling (Claude Fable)"
              },
              "has_script": true
             },
             {
              "ad_id": "1305907038126860",
              "library_url": "https://www.facebook.com/ads/library/?id=1305907038126860",
              "format": "VIDEO",
              "active": true,
              "status_read_on": "2026-11-12",
              "source": "Meta Ad Library (ad fields: dates, status, format, versions, landing link)",
              "start_date": "2026-03-31",
              "days_live": 226,
              "versions": 1,
              "landing_url": "https://epycure.com/products/boost-metabolisme?utm_source=meta&utm_medium=paid_social&utm_campaign=%7B%7Bcampaign.name%7D%7D&utm_content=%7B%7Bad.name%7D%7D",
              "eu_reach": {
               "total": 539733,
               "label": "EU reach (Meta's total)",
               "read_on": "2026-11-12",
               "source": "Meta Ad Library (public ad page, read by Poseidon)"
              }
             }
            ]
           }
          ]
         },
         "meta": {
          "endpoint": "get_brand",
          "api_version": "1.1.0",
          "credits_charged": 5,
          "credits_left": 520,
          "answered_at": "2026-11-12T22:11:37+00:00",
          "coverage": "Ads delivered in the EU or UK only. The only number of people shown is Meta's EU total reach (eu_reach, \"EU reach (Meta's total)\"), as read on the date shown. Meta publishes no spend or impressions for commercial ads. Countries, ages and genders are shown only as shares of Meta's age x gender breakdown of the same read, never as numbers of people: Meta's breakdown rows do not add up to its EU total (measured 0.93x to 1.10x on 3,820 reads), so a country figure in people would not be a fact.",
          "notes": [
           "niche = the niche of the keyword search that found the brand on the Ad Library. It is a fact about our search, not a judgment of the brand: a brand found by 'weight loss' may sell something else."
          ]
         }
        }
       }
      }
     },
     "400": {
      "description": "A parameter is refused (the answer lists the accepted values). Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "unknown format 'UGC'",
         "formats": [
          "CAROUSEL",
          "DCO"
         ],
         "note": "format is Meta's display format. Creative types such as UGC, VSL or podcast are not labelled on any ad yet. Use VIDEO for video ads."
        }
       }
      }
     },
     "401": {
      "description": "Missing, unknown or revoked API key.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "missing or invalid API key"
        }
       }
      }
     },
     "402": {
      "description": "Not enough credits for this call. Credits never expire; top up with a pack.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "not enough credits for an ad: 2 needed, 0 left. Credits never expire; top up to continue.",
         "credits_left": 0
        }
       }
      }
     },
     "404": {
      "description": "Not in Poseidon.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "ad 1234567890 is not in Poseidon"
        }
       }
      }
     },
     "429": {
      "description": "Too many requests for this key. Wait Retry-After seconds. Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "too many requests for this key (not billed)",
         "retry_after_s": 0.27
        }
       }
      }
     },
     "503": {
      "description": "Server busy or a query took too long. Retry. Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/v1/brands": {
   "get": {
    "operationId": "get_brand_by_domain",
    "summary": "A brand by its shop domain",
    "description": "A brand: its Ad Library page link, shop (link, platform), how it was found, its ads in Poseidon (counts by format, winners, median days live, ads with reach) and its 10 longest-running ads. Give page_id or the shop domain. Costs 5 credits.\n\nCost: 5 credits (€0.05); 0 if nothing is returned. MCP tool: `get_brand`.",
    "x-credits": 5,
    "x-example-request": "/v1/brands?domain=epycure.com",
    "parameters": [
     {
      "name": "domain",
      "in": "query",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "e.g. example.com"
     }
    ],
    "responses": {
     "200": {
      "description": "The answer, with `meta` (credits charged and left, coverage, notes).",
      "headers": {
       "Poseidon-API-Version": {
        "schema": {
         "type": "string"
        }
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Envelope"
        },
        "example": {
         "data": {
          "brands": [
           {
            "page_id": "2218331548432159",
            "name": "Epycure",
            "library_url": "https://www.facebook.com/ads/library/?view_all_page_id=2218331548432159",
            "niche_of_search": "weight loss",
            "found_by": "mot-clé : perte de poids",
            "first_added_on": "2026-11-11",
            "shop_check": "confirmed e-commerce shop",
            "shop_checked_on": "2026-11-12",
            "active_ads_on_meta_page": {
             "count": 420,
             "read_on": "2026-11-12"
            },
            "shop": {
             "domain": "epycure.com",
             "url": "https://epycure.com",
             "platform": "shopify",
             "checked_on": "2026-11-12",
             "check_method": "the shop's own website (its /products.json, then platform markers in its pages)"
            },
            "ads_in_poseidon": {
             "ads": 30,
             "active": 30,
             "winners_21_days": 28,
             "median_days_live": 108.5,
             "last_read": "2026-11-12",
             "ads_by_format": [
              {
               "format": "VIDEO",
               "ads": 21
              },
              {
               "format": "DCO",
               "ads": 5
              }
             ],
             "ads_with_eu_reach": 25,
             "last_reach_read": "2026-11-12",
             "computed_by": "Poseidon, from the ads above, at answer time"
            },
            "longest_running_ads": [
             {
              "ad_id": "862034243491035",
              "library_url": "https://www.facebook.com/ads/library/?id=862034243491035",
              "format": "VIDEO",
              "active": true,
              "status_read_on": "2026-11-12",
              "source": "Meta Ad Library (ad fields: dates, status, format, versions, landing link)",
              "start_date": "2026-03-08",
              "days_live": 249,
              "versions": 1,
              "landing_url": "https://epycure.com/products/boost-metabolisme",
              "eu_reach": {
               "total": 2766753,
               "label": "EU reach (Meta's total)",
               "read_on": "2026-11-12",
               "source": "Meta Ad Library (public ad page, read by Poseidon)"
              },
              "hook": {
               "text": "J'ai perdu 10 kilos en 3 mois, comment t'expliques que ça marche aussi bien ?",
               "labelled_by": "AI section labelling (Claude Fable)"
              },
              "has_script": true
             },
             {
              "ad_id": "1305907038126860",
              "library_url": "https://www.facebook.com/ads/library/?id=1305907038126860",
              "format": "VIDEO",
              "active": true,
              "status_read_on": "2026-11-12",
              "source": "Meta Ad Library (ad fields: dates, status, format, versions, landing link)",
              "start_date": "2026-03-31",
              "days_live": 226,
              "versions": 1,
              "landing_url": "https://epycure.com/products/boost-metabolisme?utm_source=meta&utm_medium=paid_social&utm_campaign=%7B%7Bcampaign.name%7D%7D&utm_content=%7B%7Bad.name%7D%7D",
              "eu_reach": {
               "total": 539733,
               "label": "EU reach (Meta's total)",
               "read_on": "2026-11-12",
               "source": "Meta Ad Library (public ad page, read by Poseidon)"
              }
             }
            ]
           }
          ]
         },
         "meta": {
          "endpoint": "get_brand",
          "api_version": "1.1.0",
          "credits_charged": 5,
          "credits_left": 515,
          "answered_at": "2026-11-12T22:11:37+00:00",
          "coverage": "Ads delivered in the EU or UK only. The only number of people shown is Meta's EU total reach (eu_reach, \"EU reach (Meta's total)\"), as read on the date shown. Meta publishes no spend or impressions for commercial ads. Countries, ages and genders are shown only as shares of Meta's age x gender breakdown of the same read, never as numbers of people: Meta's breakdown rows do not add up to its EU total (measured 0.93x to 1.10x on 3,820 reads), so a country figure in people would not be a fact.",
          "notes": [
           "niche = the niche of the keyword search that found the brand on the Ad Library. It is a fact about our search, not a judgment of the brand: a brand found by 'weight loss' may sell something else."
          ]
         }
        }
       }
      }
     },
     "400": {
      "description": "A parameter is refused (the answer lists the accepted values). Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "unknown format 'UGC'",
         "formats": [
          "CAROUSEL",
          "DCO"
         ],
         "note": "format is Meta's display format. Creative types such as UGC, VSL or podcast are not labelled on any ad yet. Use VIDEO for video ads."
        }
       }
      }
     },
     "401": {
      "description": "Missing, unknown or revoked API key.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "missing or invalid API key"
        }
       }
      }
     },
     "402": {
      "description": "Not enough credits for this call. Credits never expire; top up with a pack.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "not enough credits for an ad: 2 needed, 0 left. Credits never expire; top up to continue.",
         "credits_left": 0
        }
       }
      }
     },
     "404": {
      "description": "Not in Poseidon.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "ad 1234567890 is not in Poseidon"
        }
       }
      }
     },
     "429": {
      "description": "Too many requests for this key. Wait Retry-After seconds. Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "too many requests for this key (not billed)",
         "retry_after_s": 0.27
        }
       }
      }
     },
     "503": {
      "description": "Server busy or a query took too long. Retry. Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/v1/experiments": {
   "get": {
    "operationId": "search_experiments",
    "summary": "Brands' own A/B tests",
    "description": "Brands' own A/B tests: ads of one brand launched within 14 days that share one element and differ in another (same video with different ad texts, same text with different videos, same script with a different hook). Each variant: its ads with Ad Library links, launch day, active or stopped, days live and EU reach (Meta's total) with read date, plus the text that differs. The outcome is measured (a variant still running while another stopped, or 2x+ the reach), and absent when nothing is clear yet. Costs 5 credits (0 if nothing matches).\n\nCost: 5 credits (€0.05); 0 if nothing is returned. MCP tool: `search_experiments`.",
    "x-credits": 5,
    "x-example-request": "/v1/experiments?niche=weight%20loss&with_outcome=true&limit=2",
    "parameters": [
     {
      "name": "niche",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "niche of the keyword search that found the brand (see filters)"
     },
     {
      "name": "country",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "2-letter code: groups with at least one ad reaching that country"
     },
     {
      "name": "kind",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string",
       "enum": [
        "same_video_other_text",
        "same_text_other_video",
        "same_body_other_hook"
       ]
      }
     },
     {
      "name": "with_outcome",
      "in": "query",
      "required": false,
      "schema": {
       "type": "boolean"
      },
      "description": "true = only tests with a measured winner"
     },
     {
      "name": "limit",
      "in": "query",
      "required": false,
      "schema": {
       "type": "integer",
       "minimum": 1,
       "maximum": 20
      },
      "description": "default 10"
     },
     {
      "name": "offset",
      "in": "query",
      "required": false,
      "schema": {
       "type": "integer",
       "minimum": 0,
       "maximum": 1000
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The answer, with `meta` (credits charged and left, coverage, notes).",
      "headers": {
       "Poseidon-API-Version": {
        "schema": {
         "type": "string"
        }
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Envelope"
        },
        "example": {
         "data": {
          "filters_used": {
           "niche": "weight loss",
           "with_outcome": true,
           "limit": 2,
           "offset": 0
          },
          "matching_groups": 98,
          "groups": [
           {
            "kind": "same_text_other_video",
            "what_was_tested": "same ad text, different video",
            "brand": {
             "page_id": "957197761006622",
             "name": "Fitness-Wiola",
             "shop_domain": "fitnessviola.pl",
             "shop_url": "https://fitnessviola.pl",
             "niche_of_search": "weight loss"
            },
            "shared_ad_text": "Przez lata myślałam, że płaski brzuch wymaga głodu.\n\nMały talerz, mała porcja i ciągłe liczenie kalorii. Po kilku dniach nie dawałam rady.\n\nDziś jem duże porcje warzyw i białka 🥦\n\nPo każdym posiłku jestem najedzona.\n\nNie liczę kalorii i nie zgaduję porcji. Wszystko mam już rozpisane w menu.\n\nMenu na płaski brzuch to 10 dni gotowych posiłków z Biedronki i Lidla. Jest też gotowa lista zakupów 🛒\n\nSprzedałam już ponad 2000 takich planów.\n\nChcę jeść do syta z Menu na płaski brzuch 👇",
            "launched_between": [
             "2026-10-13",
             "2026-10-26"
            ],
            "outcome": "best reach 180,509 vs 19,465 (9.3x)",
            "found_on": "2026-11-12",
            "method": "groups-v1: 14-day window, 90 % text likeness = same, same preview = same video, catalog ads left out",
            "variants": [
             {
              "variant": 1,
              "ads": [
               {
                "ad_id": "1631528515170972",
                "library_url": "https://www.facebook.com/ads/library/?id=1631528515170972",
                "start_date": "2026-10-13",
                "active": true,
                "status_read_on": "2026-11-12",
                "days_live": 30,
                "versions": 1,
                "eu_reach": {
                 "total": 180509,
                 "label": "EU reach (Meta's total)",
                 "read_on": "2026-11-12",
                 "source": "Meta Ad Library (public ad page, read by Poseidon)"
                }
               }
              ]
             },
             {
              "variant": 2,
              "ads": [
               {
                "ad_id": "1639406481109618",
                "library_url": "https://www.facebook.com/ads/library/?id=1639406481109618",
                "start_date": "2026-10-13",
                "active": true,
                "status_read_on": "2026-11-12",
                "days_live": 30,
                "versions": 1,
                "eu_reach": {
                 "total": 144202,
                 "label": "EU reach (Meta's total)",
                 "read_on": "2026-11-12",
                 "source": "Meta Ad Library (public ad page, read by Poseidon)"
                }
               }
              ]
             }
            ]
           },
           {
            "kind": "same_text_other_video",
            "what_was_tested": "same ad text, different video",
            "brand": {
             "page_id": "1052237044630549",
             "name": "Fitness Per Negate",
             "shop_domain": "fitnesspernegate.it",
             "shop_url": "https://fitnesspernegate.it",
             "niche_of_search": "weight loss"
            },
            "shared_ad_text": "Ti sei messa a dieta. Ti sei impegnata con l’allenamento.... Ma allo specchio non vedi i risultati che speravi.\n\nTi senti più stanca, non più tonica.\n\nSui social vedi corpi perfetti e sembra tutto facile: ma per te non lo è.\n\nMa non sei tu il problema: sono gli approcci generici.\n\nOgni corpo femmile è unico.\n\nQuindi serve un approccio unico.\n\nFitness per Negate è il coaching online per ragazze che partono da zero.\n\nAllenamento semplice, alimentazione sostenibile, supporto costante.\n\n100% soddisfatta o rimborsata.\n\nUnisciti anche tu a Fitness per Negate.",
            "launched_between": [
             "2026-10-13",
             "2026-10-23"
            ],
            "outcome": "best reach 960,517 vs 13,558 (70.8x)",
            "found_on": "2026-11-12",
            "method": "groups-v1: 14-day window, 90 % text likeness = same, same preview = same video, catalog ads left out",
            "variants": [
             {
              "variant": 1,
              "ads": [
               {
                "ad_id": "2868437356861258",
                "library_url": "https://www.facebook.com/ads/library/?id=2868437356861258",
                "start_date": "2026-10-13",
                "active": true,
                "status_read_on": "2026-11-12",
                "days_live": 30,
                "versions": 1,
                "eu_reach": {
                 "total": 960517,
                 "label": "EU reach (Meta's total)",
                 "read_on": "2026-11-12",
                 "source": "Meta Ad Library (public ad page, read by Poseidon)"
                }
               }
              ]
             },
             {
              "variant": 2,
              "ads": [
               {
                "ad_id": "935577859318336",
                "library_url": "https://www.facebook.com/ads/library/?id=935577859318336",
                "start_date": "2026-10-13",
                "active": true,
                "status_read_on": "2026-11-12",
                "days_live": 30,
                "versions": 1,
                "eu_reach": {
                 "total": 373216,
                 "label": "EU reach (Meta's total)",
                 "read_on": "2026-11-12",
                 "source": "Meta Ad Library (public ad page, read by Poseidon)"
                }
               }
              ]
             }
            ]
           }
          ]
         },
         "meta": {
          "endpoint": "search_experiments",
          "api_version": "1.1.0",
          "credits_charged": 5,
          "credits_left": 510,
          "answered_at": "2026-11-12T22:11:37+00:00",
          "coverage": "Ads delivered in the EU or UK only. The only number of people shown is Meta's EU total reach (eu_reach, \"EU reach (Meta's total)\"), as read on the date shown. Meta publishes no spend or impressions for commercial ads. Countries, ages and genders are shown only as shares of Meta's age x gender breakdown of the same read, never as numbers of people: Meta's breakdown rows do not add up to its EU total (measured 0.93x to 1.10x on 3,820 reads), so a country figure in people would not be a fact.",
          "notes": [
           "A test group = ads of one brand launched within 14 days of each other that share one element and differ in another (found by Poseidon, method in each group). The outcome is measured, never judged: a variant still active while another stopped, or a best EU reach 2x or more another's. No outcome = nothing clear yet.",
           "niche = the niche of the keyword search that found the brand on the Ad Library. It is a fact about our search, not a judgment of the brand: a brand found by 'weight loss' may sell something else."
          ]
         }
        }
       }
      }
     },
     "400": {
      "description": "A parameter is refused (the answer lists the accepted values). Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "unknown format 'UGC'",
         "formats": [
          "CAROUSEL",
          "DCO"
         ],
         "note": "format is Meta's display format. Creative types such as UGC, VSL or podcast are not labelled on any ad yet. Use VIDEO for video ads."
        }
       }
      }
     },
     "401": {
      "description": "Missing, unknown or revoked API key.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "missing or invalid API key"
        }
       }
      }
     },
     "402": {
      "description": "Not enough credits for this call. Credits never expire; top up with a pack.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "not enough credits for an ad: 2 needed, 0 left. Credits never expire; top up to continue.",
         "credits_left": 0
        }
       }
      }
     },
     "429": {
      "description": "Too many requests for this key. Wait Retry-After seconds. Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "too many requests for this key (not billed)",
         "retry_after_s": 0.27
        }
       }
      }
     },
     "503": {
      "description": "Server busy or a query took too long. Retry. Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/v1/weights": {
   "get": {
    "operationId": "get_weights",
    "summary": "Weights of formats, hooks and targets",
    "description": "Weights of formats, hook types and targets from measured signals only (days live, EU reach, reach growth, versions), each with its sample size, median and top 10%. Weights under the minimum sample are never shown. Until the weighting engine runs this returns nothing. 10 credits per query (0 if nothing is returned).\n\nCost: 10 credits (€0.10); 0 if nothing is returned. MCP tool: `get_weights`.",
    "x-credits": 10,
    "x-example-request": "/v1/weights?dimension=format&niche=weight%20loss",
    "parameters": [
     {
      "name": "dimension",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      },
      "description": "format, hook_type, target_country, target_age_gender…"
     },
     {
      "name": "value",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "signal",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "niche",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "country",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "limit",
      "in": "query",
      "required": false,
      "schema": {
       "type": "integer",
       "minimum": 1,
       "maximum": 50
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The answer, with `meta` (credits charged and left, coverage, notes).",
      "headers": {
       "Poseidon-API-Version": {
        "schema": {
         "type": "string"
        }
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Envelope"
        },
        "example": {
         "data": {
          "rows": [
           {
            "dimension": "format",
            "value": "VIDEO",
            "cohort": {
             "niche": "weight loss"
            },
            "signal": "days_live",
            "sample_size": 5750,
            "median": 48.0,
            "p90": 212.0,
            "computed_at": "2026-11-12T22:08:10.629853+00:00",
            "formula_version": "weight-v2"
           },
           {
            "dimension": "format",
            "value": "VIDEO",
            "cohort": {
             "niche": "weight loss"
            },
            "signal": "active",
            "sample_size": 5750,
            "median": 1.0,
            "p90": 1.0,
            "computed_at": "2026-11-12T22:08:10.629853+00:00",
            "formula_version": "weight-v2"
           }
          ],
          "minimum_sample": 30
         },
         "meta": {
          "endpoint": "get_weights",
          "api_version": "1.1.0",
          "credits_charged": 10,
          "credits_left": 500,
          "answered_at": "2026-11-12T22:11:37+00:00",
          "coverage": "Ads delivered in the EU or UK only. The only number of people shown is Meta's EU total reach (eu_reach, \"EU reach (Meta's total)\"), as read on the date shown. Meta publishes no spend or impressions for commercial ads. Countries, ages and genders are shown only as shares of Meta's age x gender breakdown of the same read, never as numbers of people: Meta's breakdown rows do not add up to its EU total (measured 0.93x to 1.10x on 3,820 reads), so a country figure in people would not be a fact."
         }
        }
       }
      }
     },
     "400": {
      "description": "A parameter is refused (the answer lists the accepted values). Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "unknown format 'UGC'",
         "formats": [
          "CAROUSEL",
          "DCO"
         ],
         "note": "format is Meta's display format. Creative types such as UGC, VSL or podcast are not labelled on any ad yet. Use VIDEO for video ads."
        }
       }
      }
     },
     "401": {
      "description": "Missing, unknown or revoked API key.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "missing or invalid API key"
        }
       }
      }
     },
     "402": {
      "description": "Not enough credits for this call. Credits never expire; top up with a pack.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "not enough credits for an ad: 2 needed, 0 left. Credits never expire; top up to continue.",
         "credits_left": 0
        }
       }
      }
     },
     "429": {
      "description": "Too many requests for this key. Wait Retry-After seconds. Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "too many requests for this key (not billed)",
         "retry_after_s": 0.27
        }
       }
      }
     },
     "503": {
      "description": "Server busy or a query took too long. Retry. Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/v1/account": {
   "get": {
    "operationId": "account",
    "summary": "Your credits, prices and last calls",
    "description": "Free. Your credits left (they never expire), prices, last calls and credit movements.\n\nCost: free. MCP tool: `account`.",
    "x-credits": 0,
    "x-example-request": "/v1/account",
    "parameters": [],
    "responses": {
     "200": {
      "description": "The answer, with `meta` (credits charged and left, coverage, notes).",
      "headers": {
       "Poseidon-API-Version": {
        "schema": {
         "type": "string"
        }
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Envelope"
        },
        "example": {
         "data": {
          "key_id": 1,
          "owner": "<your key's label>",
          "created_at": "2026-11-12T18:06:47.539537+00:00",
          "credits": 500,
          "last_calls": [
           {
            "at": "2026-11-12T22:11:37.424394+00:00",
            "endpoint": "filters",
            "units": 0,
            "status": 200,
            "duration_ms": 0
           },
           {
            "at": "2026-11-12T22:11:37.418408+00:00",
            "endpoint": "get_weights",
            "units": 20,
            "status": 200,
            "duration_ms": 8
           }
          ],
          "last_credit_movements": [
           {
            "at": "2026-11-12T22:11:37.418408+00:00",
            "delta": -10,
            "reason": "usage: get_weights",
            "balance_after": 500
           },
           {
            "at": "2026-11-12T22:11:37.395537+00:00",
            "delta": -5,
            "reason": "usage: search_experiments",
            "balance_after": 510
           }
          ],
          "prices_in_credits": {
           "search_winners": {
            "per_call": 5
           },
           "search_experiments": {
            "per_call": 5
           },
           "get_ad": {
            "per_call": 2
           },
           "get_brand": {
            "per_call": 5
           },
           "get_weights": {
            "per_call": 10
           },
           "filters": {
            "per_call": 0
           },
           "account": {
            "per_call": 0
           }
          },
          "credits_expire": false
         },
         "meta": {
          "endpoint": "account",
          "api_version": "1.1.0",
          "credits_charged": 0,
          "credits_left": 500,
          "answered_at": "2026-11-12T22:11:37+00:00",
          "coverage": "Ads delivered in the EU or UK only. The only number of people shown is Meta's EU total reach (eu_reach, \"EU reach (Meta's total)\"), as read on the date shown. Meta publishes no spend or impressions for commercial ads. Countries, ages and genders are shown only as shares of Meta's age x gender breakdown of the same read, never as numbers of people: Meta's breakdown rows do not add up to its EU total (measured 0.93x to 1.10x on 3,820 reads), so a country figure in people would not be a fact."
         }
        }
       }
      }
     },
     "401": {
      "description": "Missing, unknown or revoked API key.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "missing or invalid API key"
        }
       }
      }
     },
     "429": {
      "description": "Too many requests for this key. Wait Retry-After seconds. Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        },
        "example": {
         "error": "too many requests for this key (not billed)",
         "retry_after_s": 0.27
        }
       }
      }
     },
     "503": {
      "description": "Server busy or a query took too long. Retry. Not billed.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/v1/packs": {
   "get": {
    "operationId": "packs",
    "summary": "Credit packs and the price of each call",
    "security": [],
    "description": "Public, no key. What the pricing page shows. Launch offer: the first 20 paying buyers get +50% credits on their first pack (added when Stripe confirms the payment).",
    "responses": {
     "200": {
      "description": "Packs and prices.",
      "content": {
       "application/json": {
        "example": {
         "packs": [
          {
           "pack": "starter",
           "price_eur": 20,
           "credits": 2000,
           "credits_expire": false,
           "bonus_percent": 0
          },
          {
           "pack": "growth",
           "price_eur": 50,
           "credits": 5500,
           "credits_expire": false,
           "bonus_percent": 10
          },
          {
           "pack": "scale",
           "price_eur": 200,
           "credits": 24000,
           "credits_expire": false,
           "bonus_percent": 20
          }
         ],
         "prices": {
          "credit_eur": 0.01,
          "empty_answer_costs": 0,
          "launch_bonus": {
           "first_purchases": 20,
           "percent": 50
          },
          "calls": [
           {
            "call": "search_winners",
            "what": "a search for winners",
            "credits": 5,
            "eur": 0.05
           },
           {
            "call": "search_experiments",
            "what": "a search of brands' own A/B tests",
            "credits": 5,
            "eur": 0.05
           },
           {
            "call": "get_ad",
            "what": "one ad in full (script sections, reach history, links)",
            "credits": 2,
            "eur": 0.02
           },
           {
            "call": "get_brand",
            "what": "a brand view",
            "credits": 5,
            "eur": 0.05
           },
           {
            "call": "get_weights",
            "what": "a weights query (format, hook or target weights)",
            "credits": 10,
            "eur": 0.1
           },
           {
            "call": "filters",
            "what": "the list of filters",
            "credits": 0,
            "eur": 0.0
           },
           {
            "call": "account",
            "what": "your account and usage",
            "credits": 0,
            "eur": 0.0
           }
          ]
         }
        }
       }
      }
     }
    },
    "x-example-request": "/v1/packs"
   }
  },
  "/v1/checkout": {
   "post": {
    "operationId": "checkout",
    "summary": "Start buying a pack (Stripe Checkout)",
    "security": [],
    "description": "Public. Send no key to buy a new key, or `Authorization: Bearer <key>` to top up that key. Redirect the browser to `url`. Credits are added when Stripe confirms the payment.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "pack"
        ],
        "properties": {
         "pack": {
          "type": "string",
          "enum": [
           "starter",
           "growth",
           "scale"
          ]
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "`url` (Stripe's payment page), `session_id`, pack, price, credits."
     },
     "400": {
      "description": "Unknown pack."
     },
     "401": {
      "description": "Invalid key (top-up)."
     },
     "502": {
      "description": "Stripe did not answer."
     },
     "503": {
      "description": "Payments are not set up, or live payments are switched off."
     }
    }
   }
  },
  "/v1/checkout/{session_id}": {
   "get": {
    "operationId": "checkout_status",
    "summary": "After payment: status, and a new buyer's key (shown once)",
    "security": [],
    "parameters": [
     {
      "name": "session_id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      }
     }
    ],
    "description": "Public; the session id is the secret. `status`: created (not confirmed yet, retry in 2 s), paid, or review (being checked by hand). A new buyer's `api_key` is in the first paid answer only.",
    "responses": {
     "200": {
      "description": "Status, credits, and for a new buyer the key once."
     },
     "404": {
      "description": "Unknown checkout."
     },
     "409": {
      "description": "The key was already shown once."
     }
    }
   }
  },
  "/v1/keys": {
   "post": {
    "operationId": "free_trial_key",
    "summary": "Get a free trial key (100 credits)",
    "security": [],
    "description": "Public. One key per email, 100 credits (€1.00), never expiring. At most 3 trials per IP address a day. The email is kept only as a hash and is not verified. The key is shown once.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "email"
        ],
        "properties": {
         "email": {
          "type": "string",
          "format": "email"
         }
        }
       }
      }
     }
    },
    "x-example-request": "/v1/keys",
    "responses": {
     "200": {
      "description": "`api_key` (shown once), `credits`, at the top level (no data envelope).",
      "content": {
       "application/json": {
        "example": {
         "api_key": "psd_<shown once>",
         "credits": 100,
         "credits_expire": false,
         "note": "Your API key, shown once: keep it. Free trial credits, worth €1.00."
        }
       }
      }
     },
     "400": {
      "description": "Not a valid email."
     },
     "409": {
      "description": "This email already has a trial key."
     },
     "429": {
      "description": "Too many trials from this IP today, or the daily cap is reached."
     }
    }
   }
  },
  "/v1/early-access": {
   "post": {
    "operationId": "early_access",
    "summary": "Join the launch list",
    "security": [],
    "description": "Public. Stores the email for the launch (a confirmation email comes later). Same answer whether the email was already on the list or not. At most 5 sign-ups per IP address an hour.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "email"
        ],
        "properties": {
         "email": {
          "type": "string",
          "format": "email"
         },
         "ref": {
          "type": [
           "string",
           "null"
          ],
          "maxLength": 40,
          "description": "the site's ?ref= value"
         },
         "page": {
          "type": [
           "string",
           "null"
          ],
          "description": "the page path the form was sent from"
         },
         "site_web": {
          "type": [
           "string",
           "null"
          ],
          "description": "anti-bot field, must stay empty"
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "`status`: on_the_list.",
      "content": {
       "application/json": {
        "example": {
         "status": "on_the_list",
         "message": "You're on the list. We'll email you when your free credits are ready."
        }
       }
      }
     },
     "400": {
      "description": "Not a valid email."
     },
     "429": {
      "description": "Too many sign-ups from this IP."
     }
    }
   }
  },
  "/v1/public/moving-now": {
   "get": {
    "operationId": "moving_now",
    "summary": "Free public list: the 20 fastest-growing EU ads right now",
    "security": [],
    "description": "Public, no key, cached 10 min, rebuilt hourly by Poseidon. One ad per brand, with its shop, format, Ad Library link and both EU reach readings (Meta's total) with their dates. No page names.",
    "responses": {
     "200": {
      "description": "The list."
     },
     "503": {
      "description": "Not published right now."
     }
    }
   }
  },
  "/v1/public/niche-pages": {
   "get": {
    "operationId": "niche_pages",
    "summary": "Free public pages: the list",
    "security": [],
    "description": "Public, no key, cached. The niche x country pages that pass (30+ ads and 4+ shops), with links.",
    "responses": {
     "200": {
      "description": "Page list (no page content in bulk)."
     },
     "404": {
      "description": "Nothing published yet."
     }
    }
   }
  },
  "/v1/public/niche-pages/{niche}/{country}": {
   "get": {
    "operationId": "niche_page",
    "summary": "Free public page: what's winning in a niche in a country",
    "security": [],
    "parameters": [
     {
      "name": "niche",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "e.g. weight-loss"
     },
     {
      "name": "country",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string"
      },
      "description": "e.g. fr"
     }
    ],
    "description": "Public, no key, cached, rebuilt daily. Counts, medians (formats only with 30+ ads), audience shares, the top ads (one per shop) and hooks, and brands' A/B tests. Every ad shown was checked against the base when the page was built.",
    "responses": {
     "200": {
      "description": "One page."
     },
     "404": {
      "description": "No such page."
     }
    }
   }
  },
  "/v1/ask/options": {
   "get": {
    "operationId": "ask_options",
    "summary": "Ask box: the choices",
    "security": [],
    "description": "Public, cacheable. Goals, niches, countries (30+ ads with reach) and formats the Ask box offers.",
    "responses": {
     "200": {
      "description": "Choices."
     }
    }
   }
  },
  "/v1/ask": {
   "post": {
    "operationId": "ask",
    "summary": "Ask box: an answer written for people (no code, no AI)",
    "security": [],
    "description": "Pick a niche, a country, an optional format and a goal (winners, hooks, ab_tests). With a key it is billed like the call it makes (a search or an A/B tests search: 5 credits; 0 if nothing matches). Without a key: 3 free tries per visitor address a day (a try that finds nothing isn't counted). Same reach rules as the rest of the API. Embeddable component: <poseidon-ask></poseidon-ask> with /embed/ask.js.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "required": [
         "niche",
         "country"
        ],
        "properties": {
         "goal": {
          "type": "string",
          "enum": [
           "winners",
           "hooks",
           "ab_tests"
          ]
         },
         "niche": {
          "type": "string"
         },
         "country": {
          "type": "string"
         },
         "format": {
          "type": "string"
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "question, items (or ab_tests), notes, credits or free tries left."
     },
     "400": {
      "description": "A choice is not accepted."
     },
     "429": {
      "description": "Free tries used for today (get a free key)."
     }
    }
   }
  },
  "/v1/stripe/webhook": {
   "post": {
    "operationId": "stripe_webhook",
    "summary": "Stripe's payment notifications (not for customers)",
    "security": [],
    "description": "Signature checked (Stripe-Signature). The same event received twice credits once.",
    "responses": {
     "200": {
      "description": "Received."
     },
     "400": {
      "description": "Bad or old signature."
     }
    }
   }
  },
  "/mcp": {
   "post": {
    "operationId": "mcp",
    "summary": "MCP server (streamable HTTP, JSON answers)",
    "description": "JSON-RPC 2.0: initialize, tools/list, tools/call, ping. Same key and same credits as REST. Tools and costs: `filters` free; `search_winners` 5 credits (€0.05); 0 if nothing is returned; `search_experiments` 5 credits (€0.05); 0 if nothing is returned; `get_ad` 2 credits (€0.02); 0 if nothing is returned; `get_brand` 5 credits (€0.05); 0 if nothing is returned; `get_weights` 10 credits (€0.10); 0 if nothing is returned; `account` free.",
    "x-mcp-tools": [
     {
      "name": "filters",
      "description": "Free. What Poseidon can filter on, with live counts: niches, Meta display formats, countries with reach data, age ranges, hook types (none measured yet), sort orders, the winner rule and the coverage note. Call it first.",
      "inputSchema": {
       "type": "object",
       "properties": {}
      },
      "x-credits": 0
     },
     {
      "name": "search_winners",
      "description": "Find winning e-commerce ads from the Meta Ad Library (EU/UK delivered ads, confirmed e-commerce shops only). Winner = live 21+ days by default. Each result: the ad's link on the Ad Library, the brand and its shop link, format, days live, versions (Meta's count of ads sharing the creative), active or stopped, EU reach (Meta's total, the only number of people) with the day it was read, the share of Meta's age x gender breakdown in the country / segment you filtered on (a share, never people), and the verbatim hook when its script was cut into sections. A field we don't have is absent, never guessed. No media and no person-name search. Costs 5 credits per search (0 if nothing matches).",
      "inputSchema": {
       "type": "object",
       "additionalProperties": false,
       "properties": {
        "niche": {
         "type": "string",
         "description": "niche of the keyword search that found the brand (see filters)"
        },
        "country": {
         "type": "string",
         "description": "2-letter code: ads with Meta reach in that country, e.g. FR"
        },
        "format": {
         "type": "string",
         "description": "Meta's display format: VIDEO, IMAGE, DCO, DPA, CAROUSEL… (creative types like UGC are not labelled yet)"
        },
        "hook_type": {
         "type": "string",
         "description": "not measured yet: returns nothing"
        },
        "age": {
         "type": "string",
         "enum": [
          "13-17",
          "18-24",
          "25-34",
          "35-44",
          "45-54",
          "55-64",
          "65+"
         ]
        },
        "gender": {
         "type": "string",
         "enum": [
          "female",
          "male"
         ]
        },
        "active": {
         "type": "boolean",
         "description": "true = still running at our last read, false = stopped"
        },
        "min_days_live": {
         "type": "integer",
         "minimum": 0,
         "description": "default 21"
        },
        "max_days_live": {
         "type": "integer",
         "minimum": 0
        },
        "min_eu_reach": {
         "type": "integer",
         "minimum": 0
        },
        "min_country_share": {
         "type": "number",
         "minimum": 0,
         "maximum": 1,
         "description": "needs country: the country's minimum share of Meta's breakdown, 0 to 1"
        },
        "min_segment_share": {
         "type": "number",
         "minimum": 0,
         "maximum": 1,
         "description": "needs age or gender: the segment's minimum share of Meta's breakdown"
        },
        "min_versions": {
         "type": "integer",
         "minimum": 0
        },
        "has_script": {
         "type": "boolean",
         "description": "true = only ads whose script is cut into sections"
        },
        "started_after": {
         "type": "string",
         "format": "date"
        },
        "started_before": {
         "type": "string",
         "format": "date"
        },
        "sort": {
         "type": "string",
         "enum": [
          "country_share",
          "days_live",
          "eu_reach",
          "newest",
          "segment_share",
          "versions"
         ],
         "description": "default: eu_reach if a country, age or gender is given, else days_live"
        },
        "limit": {
         "type": "integer",
         "minimum": 1,
         "maximum": 50,
         "description": "default 20"
        },
        "offset": {
         "type": "integer",
         "minimum": 0,
         "maximum": 1000
        }
       }
      },
      "x-credits": 5
     },
     {
      "name": "search_experiments",
      "description": "Brands' own A/B tests: ads of one brand launched within 14 days that share one element and differ in another (same video with different ad texts, same text with different videos, same script with a different hook). Each variant: its ads with Ad Library links, launch day, active or stopped, days live and EU reach (Meta's total) with read date, plus the text that differs. The outcome is measured (a variant still running while another stopped, or 2x+ the reach), and absent when nothing is clear yet. Costs 5 credits (0 if nothing matches).",
      "inputSchema": {
       "type": "object",
       "additionalProperties": false,
       "properties": {
        "niche": {
         "type": "string",
         "description": "niche of the keyword search that found the brand (see filters)"
        },
        "country": {
         "type": "string",
         "description": "2-letter code: groups with at least one ad reaching that country"
        },
        "kind": {
         "type": "string",
         "enum": [
          "same_video_other_text",
          "same_text_other_video",
          "same_body_other_hook"
         ]
        },
        "with_outcome": {
         "type": "boolean",
         "description": "true = only tests with a measured winner"
        },
        "limit": {
         "type": "integer",
         "minimum": 1,
         "maximum": 20,
         "description": "default 10"
        },
        "offset": {
         "type": "integer",
         "minimum": 0,
         "maximum": 1000
        }
       }
      },
      "x-credits": 5
     },
     {
      "name": "get_ad",
      "description": "One ad in full: Ad Library link, brand and shop, ad copy, format, dates, versions, EU reach history (Meta's total per read day; countries and age x gender as shares of Meta's breakdown), the verbatim script sentence by sentence with each sentence's section (Hook, Problem, Proof, Offer, CTA…, and who labelled it), change history and the source of every field. Costs 2 credits.",
      "inputSchema": {
       "type": "object",
       "additionalProperties": false,
       "required": [
        "ad_id"
       ],
       "properties": {
        "ad_id": {
         "type": "string",
         "description": "Ad Library id"
        }
       }
      },
      "x-credits": 2
     },
     {
      "name": "get_brand",
      "description": "A brand: its Ad Library page link, shop (link, platform), how it was found, its ads in Poseidon (counts by format, winners, median days live, ads with reach) and its 10 longest-running ads. Give page_id or the shop domain. Costs 5 credits.",
      "inputSchema": {
       "type": "object",
       "additionalProperties": false,
       "properties": {
        "page_id": {
         "type": "string"
        },
        "domain": {
         "type": "string",
         "description": "e.g. example.com"
        }
       }
      },
      "x-credits": 5
     },
     {
      "name": "get_weights",
      "description": "Weights of formats, hook types and targets from measured signals only (days live, EU reach, reach growth, versions), each with its sample size, median and top 10%. Weights under the minimum sample are never shown. Until the weighting engine runs this returns nothing. 10 credits per query (0 if nothing is returned).",
      "inputSchema": {
       "type": "object",
       "additionalProperties": false,
       "properties": {
        "dimension": {
         "type": "string",
         "description": "format, hook_type, target_country, target_age_gender…"
        },
        "value": {
         "type": "string"
        },
        "signal": {
         "type": "string"
        },
        "niche": {
         "type": "string"
        },
        "country": {
         "type": "string"
        },
        "limit": {
         "type": "integer",
         "minimum": 1,
         "maximum": 50
        }
       }
      },
      "x-credits": 10
     },
     {
      "name": "account",
      "description": "Free. Your credits left (they never expire), prices, last calls and credit movements.",
      "inputSchema": {
       "type": "object",
       "properties": {}
      },
      "x-credits": 0
     }
    ],
    "x-example-request": "/mcp",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object"
       },
       "example": {
        "jsonrpc": "2.0",
        "id": 1,
        "method": "tools/call",
        "params": {
         "name": "search_winners",
         "arguments": {
          "niche": "weight loss",
          "country": "FR",
          "format": "VIDEO",
          "limit": 1
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "JSON-RPC answer. Tool results carry the same envelope as REST in `structuredContent` and as text.",
      "content": {
       "application/json": {
        "example": {
         "jsonrpc": "2.0",
         "id": 1,
         "result": {
          "content": [
           {
            "type": "text",
            "text": "{\"data\":{\"filters_used\":{\"niche\":\"weight loss\",\"format\":\"VIDEO\",\"country\":\"FR\",\"min_days_live\":21,\"sort\":\"eu_reach\",\"limit\":1,\"offset\":0},\"matching_ads\":121,\"matching_ads_with_script\":6,\"results\":[{\"ad_id\":\"862034243491035\",\"library_url\":\"https://www.facebook.com/ads/library/?id=862034243491035\",\"format\":\"VIDEO\",\"active\":true,\"status_read_on\":\"2026-11-12\",\"source\":\"Meta Ad Library (ad fields: dates, status, format, versions, landing link)\",\"start_date\":\"2026-03-08\",\"days_live\":249,\"versions\":1,\"landing_url\":\"https://epycure.com/products/boost-metabolisme\",\"brand\":{\"page_id\":\"2218331548432159\",\"name\":\"Epycure\",\"shop_domain\":\"epycure.com\",\"shop_url\":\"https://epycure.com\",\"shop_platform\":\"shopify\",\"niche_of_search\":\"weight loss\"},\"eu_reach\":{\"total\":2766753,\"label\":\"EU reach (Meta's total)\",\"read_on\":\"2026-11-12\",\"source\":\"Meta Ad Library (public ad page, read by Poseidon)\",\"country\":{\"code\":\"FR\",\"share_of_meta_breakdown\":1.0}},\"hook\":{\"text\":\"J'ai perdu 10 kilos en 3 mois, comment t'expliques que ça marche aussi bien ?\",\"labelled_by\":\"AI section labelling (Claude Fable)\"},\"has_script\":true}]},\"meta\":{\"endpoint\":\"search_winners\",\"api_version\":\"1.1.0\",\"credits_charged\":5,\"credits_left\":495,\"answered_at\":\"2026-11-12T22:11:37+00:00\",\"coverage\":\"Ads delivered in the EU or UK only. The only number of people shown is Meta's EU total reach (eu_reach, \\\"EU reach (Meta's total)\\\"), as read on the date shown. Meta publishes no spend or impressions for commercial ads. Countries, ages and genders are shown only as shares of Meta's age x gender breakdown of the same read, never as numbers of people: Meta's breakdown rows do not add up to its EU total (measured 0.93x to 1.10x on 3,820 reads), so a country figure in people would not be a fact.\",\"notes\":[\"A winner is an ad live for at least 21 days: days between Meta's start date and its stop date, or our last read if it is still active. Override with min_days_live.\",\"niche = the niche of the keyword search that found the brand on the Ad Library. It is a fact about our search, not a judgment of the brand: a brand found by 'weight loss' may sell something else.\",\"More results: call again with offset=1.\",\"Only 6 of the 121 matching ads have their script transcribed and cut into sections; the others have not been transcribed yet. Say so instead of describing their content.\"]}}"
           }
          ],
          "isError": false,
          "structuredContent": {
           "data": {
            "filters_used": {
             "niche": "weight loss",
             "format": "VIDEO",
             "country": "FR",
             "min_days_live": 21,
             "sort": "eu_reach",
             "limit": 1,
             "offset": 0
            },
            "matching_ads": 121,
            "matching_ads_with_script": 6,
            "results": [
             {
              "ad_id": "862034243491035",
              "library_url": "https://www.facebook.com/ads/library/?id=862034243491035",
              "format": "VIDEO",
              "active": true,
              "status_read_on": "2026-11-12",
              "source": "Meta Ad Library (ad fields: dates, status, format, versions, landing link)",
              "start_date": "2026-03-08",
              "days_live": 249,
              "versions": 1,
              "landing_url": "https://epycure.com/products/boost-metabolisme",
              "brand": {
               "page_id": "2218331548432159",
               "name": "Epycure",
               "shop_domain": "epycure.com",
               "shop_url": "https://epycure.com",
               "shop_platform": "shopify",
               "niche_of_search": "weight loss"
              },
              "eu_reach": {
               "total": 2766753,
               "label": "EU reach (Meta's total)",
               "read_on": "2026-11-12",
               "source": "Meta Ad Library (public ad page, read by Poseidon)",
               "country": {
                "code": "FR",
                "share_of_meta_breakdown": 1.0
               }
              },
              "hook": {
               "text": "J'ai perdu 10 kilos en 3 mois, comment t'expliques que ça marche aussi bien ?",
               "labelled_by": "AI section labelling (Claude Fable)"
              },
              "has_script": true
             }
            ]
           },
           "meta": {
            "endpoint": "search_winners",
            "api_version": "1.1.0",
            "credits_charged": 5,
            "credits_left": 495,
            "answered_at": "2026-11-12T22:11:37+00:00",
            "coverage": "Ads delivered in the EU or UK only. The only number of people shown is Meta's EU total reach (eu_reach, \"EU reach (Meta's total)\"), as read on the date shown. Meta publishes no spend or impressions for commercial ads. Countries, ages and genders are shown only as shares of Meta's age x gender breakdown of the same read, never as numbers of people: Meta's breakdown rows do not add up to its EU total (measured 0.93x to 1.10x on 3,820 reads), so a country figure in people would not be a fact.",
            "notes": [
             "A winner is an ad live for at least 21 days: days between Meta's start date and its stop date, or our last read if it is still active. Override with min_days_live.",
             "niche = the niche of the keyword search that found the brand on the Ad Library. It is a fact about our search, not a judgment of the brand: a brand found by 'weight loss' may sell something else."
            ]
           }
          }
         }
        }
       }
      }
     },
     "202": {
      "description": "Notification accepted (no body)."
     },
     "401": {
      "description": "Missing or invalid key."
     }
    }
   }
  }
 },
 "components": {
  "securitySchemes": {
   "bearer": {
    "type": "http",
    "scheme": "bearer",
    "description": "Your key, psd_… (or the X-API-Key header)."
   }
  },
  "schemas": {
   "Envelope": {
    "type": "object",
    "required": [
     "data",
     "meta"
    ],
    "properties": {
     "data": {
      "type": "object",
      "description": "The answer. Fields Poseidon doesn't have are absent."
     },
     "meta": {
      "type": "object",
      "properties": {
       "endpoint": {
        "type": "string"
       },
       "api_version": {
        "type": "string"
       },
       "credits_charged": {
        "type": "integer"
       },
       "credits_left": {
        "type": "integer"
       },
       "answered_at": {
        "type": "string",
        "format": "date-time"
       },
       "coverage": {
        "type": "string"
       },
       "notes": {
        "type": "array",
        "items": {
         "type": "string"
        }
       }
      }
     }
    }
   },
   "Error": {
    "type": "object",
    "required": [
     "error"
    ],
    "properties": {
     "error": {
      "type": "string"
     },
     "note": {
      "type": "string"
     },
     "retry_after_s": {
      "type": "number"
     },
     "credits_left": {
      "type": "integer"
     }
    },
    "additionalProperties": true
   }
  }
 }
}
