{"openapi":"3.1.0","info":{"title":"Foreplay Public API","description":"\nWelcome to the Foreplay Public API documentation! This API empowers you to programmatically search, filter, and analyze a vast database of ads and brands.\n\nThis document is designed to get you up and running as quickly as possible. We'll cover everything from your first API call to advanced features.\n\n## Getting Started\n\nFollow these four simple steps to make your first request.\n\n### Step 1: Get Your API Key\n\nBefore you can do anything, you need an API key. You can generate one from your account dashboard.\n\n➡️ **[Get your API Key from the Foreplay Data API Dashboard](https://app.foreplay.co/api-overview)**\n\n### Step 2: Prepare Your Request\n\nEvery request to the API requires your key to be sent in the `Authorization` header.\n\n```http\nAuthorization: YOUR_API_KEY\n```\n\n*Replace `YOUR_API_KEY` with the key you generated in Step 1.*\n\n### Step 3: Make Your First Call\n\nLet's fetch a list of brands. Open your terminal or API client and use the following cURL command:\n\n**Example: Fetching a list of brands**\n\n```bash\n# This command requests the first 10 brands from the Spyder endpoint.\ncurl -X GET \"https://public.api.foreplay.co/api/spyder/brands?offset=0&limit=10\" \n  -H \"Authorization: YOUR_API_KEY\"\n```\n\n### Step 4: Understand the Response\n\nIf successful, you'll receive a JSON object containing the requested data and some metadata about your request.\n\n```json\n{\n  \"data\": [\n    {\n      \"id\": \"brand_123456\",\n      \"name\": \"Example Brand Inc.\",\n      // ... other brand details\n    }\n  ],\n  \"metadata\": {\n    \"success\": true,\n    \"status_code\": 200,\n    \"count\": 1\n    // ... other metadata\n  }\n}\n```\nCongratulations, you've successfully made your first API call!\n\n---\n\n## Core Concepts\n\nTo get the most out of the API, it helps to understand these key features:\n\n*   **SwipeFile**: Your personal collection of saved ads. Use the `/api/swipefile/ads` endpoint to access ads you've saved for inspiration or analysis.\n*   **Boards**: Boards are collections you create to organize brands. This is perfect for tracking competitors or grouping brands by category.\n*   **Spyder**: This feature allows you to access brands you are subscribed to, providing a focused view of the data that matters most to you.\n*   **Discovery**: The discovery endpoints (`/api/discovery/*`) are for broad searches across the entire database, helping you find new ads and brands based on keywords and filters.\n\n---\n\n## Common Use Cases\n\nHere are a few ways you can use the Foreplay API:\n\n*   **Competitive Intelligence**: Monitor the advertising strategies of competing brands.\n*   **Market Research**: Analyze advertising trends across different niches and platforms.\n*   **Creative Inspiration**: Discover high-performing ad creatives to inspire your own campaigns.\n*   **Building Custom Dashboards**: Integrate ad data directly into your internal analytics tools.\n\n---\n\n## API Reference\n\n### Base URL\n\nAll API endpoints are relative to the following base URL:\n\n**[https://public.api.foreplay.co/](https://public.api.foreplay.co/)**\n\n### Authentication\n\nEvery request must include your API key in the `Authorization` header.\n\n**Code Examples**\n\n**JavaScript (fetch)**\n```js\n// Fetches the first 10 brands from the Spyder endpoint.\nawait fetch(\"https://public.api.foreplay.co/api/spyder/brands?offset=0&limit=10\", {\n  headers: { Authorization: \"YOUR_API_KEY\" }\n});\n```\n\n**Python (requests)**\n```python\nimport requests\n\n# Fetches the first 10 brands from the Spyder endpoint.\nr = requests.get(\n    \"https://public.api.foreplay.co/api/spyder/brands\",\n    params={\"offset\": 0, \"limit\": 10},\n    headers={\"Authorization\": \"YOUR_API_KEY\"},\n)\n```\n\n**Security Tips**\n*   Keep your API keys secret and never commit them to public source control.\n*   Rotate keys immediately if you suspect they have been exposed.\n\n### Credit Usage\n\nYour usage is limited by the number of credits available in your subscription plan.\n\n**How Credits are Computed**\n\n| Action | Cost | Example |\n| :--- | :--- | :--- |\n| Retrieving Ads | 1 credit per ad returned | A request returning 25 ads costs 25 credits. |\n| Retrieving Brands | 1 credit per **request** | A request returning 1 or 10 brands both cost 1 credit. |\n\n**How to Monitor Your Usage**\n1.  Check the `X-Credits-Remaining` header in any API response.\n2.  Use the `/usage` endpoint, which does not consume any credits.\n3.  View your usage history in the [API Logs Dashboard](https://app.foreplay.co/api-logs).\n\n### Response Headers\n\nAll responses include the following headers for your convenience:\n\n-   `X-Process-Time`: The server time taken to process the request.\n-   `X-Trace-ID`: A unique ID for each request, useful for support and debugging.\n-   `X-Credits-Remaining`: The number of credits left on your plan.\n-   `X-Credit-Cost`: The credit cost of the specific request you just made.\n\n### Error Handling\n\nThe API uses standard HTTP status codes to indicate the success or failure of a request.\n\n| Status Code | Meaning | Possible Cause |\n| :--- | :--- | :--- |\n| `400 Bad Request` | Your request was malformed. | Invalid parameters, incorrect data types. |\n| `401 Unauthorized` | Authentication failed. | Missing or invalid API key. |\n| `402 Payment Required` | You have run out of credits. | Your usage limit has been reached. |\n| `403 Forbidden` | You don't have permission. | Your plan doesn't include access to this feature. |\n| `404 Not Found` | The requested resource does not exist. | An incorrect ID was used. |\n| `429 Too Many Requests` | You have exceeded the rate limit. | Sending too many requests in a short period. |\n| `500 Internal Server Error` | Something went wrong on our end. | A temporary issue with our servers. |\n\n**Example Error Response (`401 Unauthorized`)**\n```json\n{\n  \"metadata\": {\n    \"success\": false,\n    \"message\": \"Authentication failed.\",\n    \"status_code\": 401,\n    \"processed_at\": 1756488402833\n  },\n  \"error\": {\n    \"message\": \"Invalid or missing API key\"\n  },\n  \"data\": []\n}\n```\n\n### Pagination\n\nFor endpoints that return a list of items, we use two methods of pagination:\n\n1.  **Cursor-based**: Use the `cursor` value from a response's metadata to fetch the next page of results. Cursor will always be in the `metadata.cursor` field in the response if the endpoint supports cursor based pagination. Please keep the same limit for each subsequent request.\n\n2.  **Offset-based**: Use the `offset` and `limit` parameters to page through results. This method is used in some endpoints, you need to increment by the limit for each subsequent request. i.e. if you request `limit=10` and get 10 results, your next request should be `offset=10&limit=10`.\n\nAlways check the specific endpoint's documentation to see which method is supported. The `limit` parameter controls the number of results per page (max is typically 250, but may vary per endpoint).\n\n### Rate Limiting\n\nTo ensure fair usage, the API enforces rate limits. If you exceed the limit, you will receive a `429 Too Many Requests` error. We recommend implementing a retry mechanism with exponential backoff if you anticipate making a high volume of requests.\n\n---\n\n## Understanding the Data Model\n\nThe API revolves around two main entities: **Ads** and **Brands**.\n\n### Ad\n\nAn Ad represents a single advertisement. Key fields include:\n\n- `id`: Unique identifier for the ad.\n- `ad_id`: Platform-specific ad identifier.\n- `name`: Name or headline of the ad.\n- `brand_id`: The brand associated with the ad.\n- `description`: Text description of the ad.\n- `headline`: Headline or main text of the ad.\n- `cta_title`: Call-to-action text (e.g., \"Shop Now\").\n- `categories`: List of categories the ad belongs to.\n- `creative_targeting`: Targeting information for the ad.\n- `languages`: Languages used in the ad.\n- `market_target`: Market segment (e.g., B2B, B2C).\n- `niches`: List of niches relevant to the ad.\n- `product_category`: Product category advertised.\n- `timestamped_transcription`: List of transcription segments with timestamps (for video ads).\n- `full_transcription`: Complete transcription text (for video ads).\n- `cards`: Array of cards/components (for carousel, DCO, DPA, multi-media ads).\n- `avatar`: URL to the ad's avatar image.\n- `cta_type`: Type of call-to-action.\n- `display_format`: Format of the ad (e.g., video, image, carousel).\n- `emotional_drivers`: Emotional drivers detected in the ad.\n- `link_url`: Destination URL for the ad.\n- `live`: Whether the ad is currently live.\n- `persona`: Target persona (age, gender).\n- `publisher_platform`: Platforms where the ad is published.\n- `started_running`: Timestamp when the ad started running.\n- `thumbnail`: URL to the ad's thumbnail image.\n- `video`: URL to the ad's video (if applicable).\n- `image`: URL to the ad's image (if applicable).\n- `content_filter`: Content classification scores.\n- `video_duration`: Duration of the video (if applicable).\n- `running_duration`: Calculated duration the ad has been running.\n\n### Transcription\n\nAd transcriptions are automatically generated for video/audio content and included in all ad objects returned by the Foreplay Public API. No additional API calls or premium access required.\n\n**Field Types:**\n\n- `full_transcription` - Complete text of spoken content as a string\n- `timestamped_transcription` - Array of objects with startTime, endTime, and sentence fields\n\n**Transcription Locations by Ad Type:**\n\n- Video Ads\n\n**Location:** Main ad object  \n**Fields:** `full_transcription` and `timestamped_transcription`  \n**Example:** Single video ad has transcription data directly in the ad response\n\n- Image Ads\n\n**Location:** Main ad object  \n**Fields:** `full_transcription: null` and `timestamped_transcription: null`  \n**Note:** No transcriptions since there's no audio content\n\n- Carousel Ads\n\n**Location:** Within `cards` array  \n**Fields:** Each card has its own `full_transcription` and `timestamped_transcription`  \n**Structure:** Image cards = null/empty, video cards = actual transcription data\n\n- DCO (Dynamic Creative Optimization) Ads\n\n**Location:** Within `cards` array  \n**Fields:** Each component has its own `full_transcription` and `timestamped_transcription`  \n**Structure:** Same as carousel - transcriptions per component based on media type\n\n- DPA (Dynamic Product Ads)\n\n**Location:** Within `cards` array  \n**Fields:** Each product card has its own `full_transcription` and `timestamped_transcription`  \n**Structure:** Typically null/empty since DPA cards are usually product images\n\n- Multi-Video/Multi-Media Ads\n\n**Location:** Within `cards` array (when present)  \n**Fields:** Individual transcriptions per video component  \n**Fallback:** May also appear at main ad level if single aggregated transcription\n\n**Implementation Notes:**\n\n- **Always available:** Transcriptions are included in every ad endpoint response\n- **No filtering needed:** Check for null/empty values to determine if content has audio\n- **Consistent structure:** Same field names across all ad types and endpoints\n- **Error handling:** Empty arrays or null values indicate no transcribable content\n\n**Key Rule:** If an ad has a `cards` array, check there first for component-level transcriptions. Main ad level transcriptions are for single-media ads.\n\n### Brand\n\nA Brand represents a company or entity that runs ads. Key fields include:\n\n- `id`: Unique identifier for the brand.\n- `name`: The name of the brand.\n- `description`: Optional text description of the brand.\n- `category`: Category or vertical the brand belongs to.\n- `niches`: List of niches relevant to the brand.\n- `verification_status`: Verification status of the brand.\n- `url`: Main website URL for the brand.\n- `websites`: List of additional website URLs.\n- `avatar`: URL to the brand's avatar image.\n- `ad_library_id`: Platform-specific ad library identifier.\n- `is_delegate_page_with_linked_primary_profile`: Indicates if the brand page is a delegate with a linked primary profile.\n\n---\n\n## MCP Integration\n\nThe Foreplay Public API exposes an MCP server for easy integration with LLMs and other tools.\n\n### Base URL\n\nThe MCP server is available at:\n\n**[https://public.api.foreplay.co/mcp](https://public.api.foreplay.co/mcp)**\n\n### Features\n\n- **HTTP Endpoints**: Access all API endpoints via MCP's HTTP interface.\n- **SSE Endpoints**: Stream data in real-time using MCP's Server-Sent Events (SSE) interface.\n- **Automatic Documentation**: Use the built-in MCP documentation for easy reference.\n\n### Authentication\n\nMCP uses the same API key authentication as the main API. Include your key in the `Authorization` header for all MCP requests.\n\n\n---\n\n## Best Practices\n\n-   **Timezones**: Always specify time in UTC format. To get all ads for a full day, use a range like `start_date=YYYY-MM-DD 00:00:00` and `end_date=YYYY-MM-DD 23:59:59`.\n-   **Error Checking**: Don't assume every request will succeed. Check the HTTP status code and error messages to handle failures gracefully.\n-   **Efficient Pagination**: Use the `cursor` parameter for pagination whenever possible to ensure you don't miss data.\n\n---\n\n## FAQ\n\n**How do I get all ads for a specific day?**\nUse the `start_date` and `end_date` parameters with a full 24-hour range. For example: `start_date=2024-12-01 00:00:00` and `end_date=2024-12-01 23:59:59`.\n\n**How do I paginate through a large list of ads?**\nCheck the `metadata` object in the response for a `cursor` value. Pass this value in your next request as a query parameter to get the next page.\n\n**What is the difference between `offset` and `cursor` pagination?**\n`offset` skips a specific number of records, which can be inefficient for deep pages. `cursor` points to a specific record, making it faster and more reliable for fetching subsequent pages. Always check the specific endpoint's documentation to see which method is supported.\n\n---\n","termsOfService":"https://www.foreplay.co/page/terms-of-service","version":"0.26.3b"},"paths":{"/api/swipefile/ads":{"get":{"tags":["SwipeFile"],"summary":"Get user's swipefile ads, all saved ads in swipefile","description":"List every ad the user has saved to their swipefile. Each result is a full ad object (media URLs, transcription, brand, etc.) — that's why this stays per-item rather than flat-rate like the boards listing.\n\n**Credit cost:** 1 credit per ad returned. Empty / error responses are free.\n\n**Viewing ads on Foreplay:** Each ad in the response includes a `foreplay_url` field — the canonical platform URL for that ad, computed server-side from its internal `id`. When you reference a specific ad in a reply, render `foreplay_url` directly as a markdown link (e.g. `[See in Foreplay](<foreplay_url>)`). Do this proactively, not only when asked. NEVER invent alternate paths like `/discover/ad/{id}` — that path does not exist; only `https://app.foreplay.co/discovery?ad={id}` resolves.","operationId":"get_swipefile_ads","parameters":[{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To get all ads from a specific day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59.","examples":["2024-11-12 00:00:00","2024-11-12T00:00:00","2024-11-12"],"title":"Start Date"},"description":"Start date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To get all ads from a specific day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To include all results for a given day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59.","examples":["2024-11-12 23:59:59","2024-11-13","2024-11-12T23:59:59"],"title":"End Date"},"description":"End date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To include all results for a given day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59."},{"name":"live","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/Live"},{"type":"null"}],"description":"Filter ads by live status. `true` means currently active ads, `false` means inactive ads. Leave empty to include both.","examples":["true","false"],"title":"Live"},"description":"Filter ads by live status. `true` means currently active ads, `false` means inactive ads. Leave empty to include both."},{"name":"display_format","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/DisplayFormat"}},{"type":"null"}],"description":"Filter ads by display format. Multiple formats can be specified. Available formats: video, image, carousel, dco, story, reels.\n\nExample: `?display_format=video&display_format=carousel`\n\n","examples":["video","carousel","image","dco"],"title":"Display Format"},"description":"Filter ads by display format. Multiple formats can be specified. Available formats: video, image, carousel, dco, story, reels.\n\nExample: `?display_format=video&display_format=carousel`\n\n"},{"name":"publisher_platform","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/PublisherPlatform"}},{"type":"null"}],"description":"Filter ads by publisher platform. Multiple platforms can be specified. Available platforms: facebook, instagram, audience_network, messenger.\n\nExample: `?publisher_platform=facebook&publisher_platform=instagram`\n\n","examples":["facebook","instagram","facebook","audience_network"],"title":"Publisher Platform"},"description":"Filter ads by publisher platform. Multiple platforms can be specified. Available platforms: facebook, instagram, audience_network, messenger.\n\nExample: `?publisher_platform=facebook&publisher_platform=instagram`\n\n"},{"name":"niches","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/Niche"}},{"type":"null"}],"description":"Filter ads by niche/category. Multiple niches can be specified. Available niches: travel, fashion, food, technology, sports, beauty, etc.\n\nExample: `?niches=travel&niches=fashion`\n\n","examples":[["accessories"],["fashion"],["food/drink"],["app/software"]],"title":"Niches"},"description":"Filter ads by niche/category. Multiple niches can be specified. Available niches: travel, fashion, food, technology, sports, beauty, etc.\n\nExample: `?niches=travel&niches=fashion`\n\n"},{"name":"market_target","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/MarketTarget"}},{"type":"null"}],"description":"Filter ads by market target. Multiple targets can be specified. Available targets: b2b, b2c.\n\nExample: `?market_target=b2b`\n\n","examples":["b2c","b2b","b2b","b2c"],"title":"Market Target"},"description":"Filter ads by market target. Multiple targets can be specified. Available targets: b2b, b2c.\n\nExample: `?market_target=b2b`\n\n"},{"name":"languages","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/Language"}},{"type":"null"}],"description":"Filter ads by language. Multiple languages can be specified. Available languages: en, fr, es, de, it, pt, etc.\n\nExample: `?languages=en&languages=fr`\n\n","examples":["en","es","de","fr"],"title":"Languages"},"description":"Filter ads by language. Multiple languages can be specified. Available languages: en, fr, es, de, it, pt, etc.\n\nExample: `?languages=en&languages=fr`\n\n"},{"name":"video_duration_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"description":"Filter ads by minimum video duration in seconds. Only applies to video ads.","examples":[5.0,10.0,30.0],"title":"Video Duration Min"},"description":"Filter ads by minimum video duration in seconds. Only applies to video ads."},{"name":"video_duration_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"description":"Filter ads by maximum video duration in seconds. Only applies to video ads.","examples":[60.0,120.0,300.0],"title":"Video Duration Max"},"description":"Filter ads by maximum video duration in seconds. Only applies to video ads."},{"name":"running_duration_min_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Filter ads by minimum running duration in days. Must be a positive integer.","examples":[1,7,30],"title":"Running Duration Min Days"},"description":"Filter ads by minimum running duration in days. Must be a positive integer."},{"name":"running_duration_max_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Filter ads by maximum running duration in days. Must be a positive integer.","examples":[7,30,90],"title":"Running Duration Max Days"},"description":"Filter ads by maximum running duration in days. Must be a positive integer."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Pagination offset. Use in conjunction with `limit` to paginate results. Default is 0.","examples":[0,10,50,100],"default":0,"title":"Offset"},"description":"Pagination offset. Use in conjunction with `limit` to paginate results. Default is 0."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":250,"exclusiveMinimum":0,"description":"Pagination limit (max 250). Controls the number of ads returned per request.","examples":[5,10,50,100,250],"default":10,"title":"Limit"},"description":"Pagination limit (max 250). Controls the number of ads returned per request."},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/SwipefileSortOrder"},{"type":"null"}],"description":"Order of results: 'saved_newest' (default), 'newest', 'oldest', 'longest_running', or 'most_relevant'. Sorts ads by creation date, or by longest running duration, or by relevance to search query, or by saved date in swipefile.","examples":["newest","oldest","longest_running","most_relevant","saved_newest"],"default":"saved_newest","title":"Order"},"description":"Order of results: 'saved_newest' (default), 'newest', 'oldest', 'longest_running', or 'most_relevant'. Sorts ads by creation date, or by longest running duration, or by relevance to search query, or by saved date in swipefile."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdListResponse"}}}},"400":{"description":"Bad Request - Invalid parameters provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment Required - Insufficient credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Access denied to SwipeFile features","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - No ads found for user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error - Database connection issues","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/boards":{"get":{"tags":["Boards"],"summary":"List the authenticated user's boards (optionally with folders)","description":"Return the boards the authenticated user has access to.\n\nTwo response shapes, picked by the `folders` query parameter.\n\n──── folders=false (default) ────────────────────────────────────\nReturns a flat list of boards. `limit` controls page size:\n  • limit omitted → ALL boards for the user/team in one response\n  • limit set     → paginated (offset/limit honoured)\n\nExample response:\n  {\n    \"data\": [\n      { \"id\": \"b1\", \"name\": \"Competitor analysis\",\n        \"description\": \"\", \"created_at\": 1737912010 },\n      { \"id\": \"b2\", \"name\": \"Inspiration\",\n        \"description\": \"\", \"created_at\": 1738000000 }\n    ],\n    \"metadata\": { \"success\": true, \"status_code\": 200 }\n  }\n\n──── folders=true ──────────────────────────────────────────────\nReturns a NESTED TREE — every folder carries its boards inline.\nFolders are sorted with the synthesised DEFAULT folder always\nfirst (it holds boards not assigned to any user-created folder,\nand is always emitted even when empty), then user folders\nalphabetically by `name`, case-insensitive.\n\nFolder objects expose `name`, `is_default`, `created_at`,\n`created_by`, `team_id`, and `boards`. NO folder id is exposed:\nconsumers identify folders by name (or by `is_default=true` for\nthe default one).\n\nA board can belong to multiple user folders (the Firestore\nrelation is `folders.boardIds: string[]`). When that happens\nthe board appears in each parent folder's `boards` array — no\ndedup, consumer handles it if needed. Boards that belong to at\nleast one user folder are NOT also placed in the default\nfolder; default only holds the unassigned ones.\n\n`offset` / `limit` are silently ignored on this path; the tree\nis always returned in full.\n\nExample response:\n  {\n    \"data\": {\n      \"folders\": [\n        { \"name\": \"Default\", \"is_default\": true,\n          \"created_at\": null, \"created_by\": null,\n          \"team_id\": null,\n          \"boards\": [\n            { \"id\": \"b3\", \"name\": \"Quick saves\",\n              \"description\": \"\", \"created_at\": ... }\n          ]\n        },\n        { \"name\": \"AG1 launch\", \"is_default\": false,\n          \"created_at\": 1737912010,\n          \"created_by\": \"...\", \"team_id\": null,\n          \"boards\": [\n            { \"id\": \"b1\", \"name\": \"...\",\n              \"description\": \"\", \"created_at\": ... }\n          ]\n        },\n        { \"name\": \"Inspiration\", \"is_default\": false,\n          \"created_at\": ..., \"boards\": [] }\n      ]\n    },\n    \"metadata\": { \"success\": true, \"status_code\": 200 }\n  }\n\n──── Credit cost ───────────────────────────────────────────────\nThis endpoint is FLAT-RATE: 1 credit per successful call,\nregardless of how many boards / folders are returned. Empty /\nno-data responses are free.\n\n──── Scoping ───────────────────────────────────────────────────\nIf the authenticated user is on a team, boards and folders are\nfiltered to that team (`teamId`). Otherwise they're filtered to\nthe user (`createdBy`/`created_by`). The auth layer picks the\nright scope from the API key — no parameter to set.","operationId":"get_boards","parameters":[{"name":"folders","in":"query","required":false,"schema":{"type":"boolean","description":"When false (default), the response is a flat array of boards. When true, the response is a nested tree: {folders: [{...folder, boards: [...]}], unfiled_boards: [...]} — each folder carries its full boards inline (no client-side join needed) and boards that don't belong to any folder are exposed as `unfiled_boards`. `offset` / `limit` are ignored when folders=true; the tree is always returned in full.","default":false,"title":"Folders"},"description":"When false (default), the response is a flat array of boards. When true, the response is a nested tree: {folders: [{...folder, boards: [...]}], unfiled_boards: [...]} — each folder carries its full boards inline (no client-side join needed) and boards that don't belong to any folder are exposed as `unfiled_boards`. `offset` / `limit` are ignored when folders=true; the tree is always returned in full."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Offset for pagination. Only used when `limit` is also set and `folders=false`. Ignored otherwise.","default":0,"title":"Offset"},"description":"Offset for pagination. Only used when `limit` is also set and `folders=false`. Ignored otherwise."},{"name":"limit","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","exclusiveMinimum":0},{"type":"null"}],"description":"Page size when `folders=false`. Omit (or pass nothing) to return ALL boards for the user/team. Ignored when `folders=true` (the tree is always full).","title":"Limit"},"description":"Page size when `folders=false`. Omit (or pass nothing) to return ALL boards for the user/team. Ignored when `folders=true` (the tree is always full)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BoardListResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment Required - Insufficient credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Access denied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - No boards found for user.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error - Database connection issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/board/brands":{"get":{"tags":["Board"],"summary":"Get all brands for a specific board","description":"Retrieve every brand associated with a board the caller already owns. Returns brand metadata (id, name, domain, logo) — for the per-board AD content see `/api/board/ads`.\n\nPagination supported via `offset` and `limit` query parameters.\n\n**Credit cost:** 1 credit per call, flat-rate — regardless of how many brands the board contains. Listing your own library is one operation. Empty / error responses are free.","operationId":"get_brands_by_board_id","parameters":[{"name":"board_id","in":"query","required":true,"schema":{"type":"string","minLength":1,"description":"The ID of the board to retrieve brands from.","title":"Board Id"},"description":"The ID of the board to retrieve brands from."},{"name":"offset","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"The offset for pagination. (int)","default":0,"title":"Offset"},"description":"The offset for pagination. (int)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":10,"exclusiveMinimum":0,"description":"Pagination limit (max 10). (int)","default":10,"title":"Limit"},"description":"Pagination limit (max 10). (int)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandListResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment Required - Insufficient credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Access denied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - No brands found for board.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error - Database connection issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/board/ads":{"get":{"tags":["Board"],"summary":"Get Ads by board id and Filters","description":"Retrieve ads associated with a specific board ID, applying the supplied filters. Pagination via cursor-based navigation.\n\n**Credit cost:** 1 credit per ad returned. Each ad in the response is a full creative asset (media URLs, transcription, metadata). Empty / error responses are free.\n\n**Viewing ads on Foreplay:** Each ad in the response includes a `foreplay_url` field — the canonical platform URL for that ad, computed server-side from its internal `id`. When you reference a specific ad in a reply, render `foreplay_url` directly as a markdown link (e.g. `[See in Foreplay](<foreplay_url>)`). Do this proactively, not only when asked. NEVER invent alternate paths like `/discover/ad/{id}` — that path does not exist; only `https://app.foreplay.co/discovery?ad={id}` resolves.","operationId":"get_board_ads","parameters":[{"name":"board_id","in":"query","required":true,"schema":{"type":"string","description":"Board ID to search for","title":"Board Id"},"description":"Board ID to search for"},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To get all ads from a specific day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59.","examples":["2024-11-12 00:00:00","2024-11-12T00:00:00","2024-11-12"],"title":"Start Date"},"description":"Start date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To get all ads from a specific day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To include all results for a given day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59.","examples":["2024-11-12 23:59:59","2024-11-13","2024-11-12T23:59:59"],"title":"End Date"},"description":"End date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To include all results for a given day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59."},{"name":"live","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/Live"},{"type":"null"}],"description":"Filter ads by live status. `true` means currently active, `false` means not live.","title":"Live"},"description":"Filter ads by live status. `true` means currently active, `false` means not live."},{"name":"display_format","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/DisplayFormat"}},{"type":"null"}],"description":"Filter by one or more display formats.\nExample: `?display_format=video&display_format=carousel`\n\n","title":"Display Format"},"description":"Filter by one or more display formats.\nExample: `?display_format=video&display_format=carousel`\n\n"},{"name":"publisher_platform","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/PublisherPlatform"}},{"type":"null"}],"description":"Filter by one or more publisher platforms.\nExample: `?publisher_platform=facebook&publisher_platform=instagram`\n\n","title":"Publisher Platform"},"description":"Filter by one or more publisher platforms.\nExample: `?publisher_platform=facebook&publisher_platform=instagram`\n\n"},{"name":"niches","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/Niche"}},{"type":"null"}],"description":"Filter by one or more niches.\nExample: `?niche=travel&niche=other`\n\n","examples":[["accessories"],["fashion"],["food/drink"],["app/software"]],"title":"Niches"},"description":"Filter by one or more niches.\nExample: `?niche=travel&niche=other`\n\n"},{"name":"market_target","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/MarketTarget"}},{"type":"null"}],"description":"Filter by market target.\nExample: `?market_target=b2b`\n\n","title":"Market Target"},"description":"Filter by market target.\nExample: `?market_target=b2b`\n\n"},{"name":"languages","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/Language"}},{"type":"null"}],"description":"Filter by languages.\nAccepts values like 'french', 'FR', 'romanian', 'ro', etc.","title":"Languages"},"description":"Filter by languages.\nAccepts values like 'french', 'FR', 'romanian', 'ro', etc."},{"name":"video_duration_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"description":"Filter ads by minimum video duration in seconds. Only applies to video ads.","examples":[5.0,10.0,30.0],"title":"Video Duration Min"},"description":"Filter ads by minimum video duration in seconds. Only applies to video ads."},{"name":"video_duration_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"description":"Filter ads by maximum video duration in seconds. Only applies to video ads.","examples":[60.0,120.0,300.0],"title":"Video Duration Max"},"description":"Filter ads by maximum video duration in seconds. Only applies to video ads."},{"name":"running_duration_min_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Filter ads by minimum running duration in days. Must be a positive integer.","examples":[1,7,30],"title":"Running Duration Min Days"},"description":"Filter ads by minimum running duration in days. Must be a positive integer."},{"name":"running_duration_max_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Filter ads by maximum running duration in days. Must be a positive integer.","examples":[7,30,90],"title":"Running Duration Max Days"},"description":"Filter ads by maximum running duration in days. Must be a positive integer."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Cursor for pagination. Use the cursor value from the previous response's metadata to get the next page of results.","examples":["eyJ0cyI6MTcwOTY1NDQwMDAwMCwiaWQiOiJhYmMxMjMifQ=="],"title":"Cursor"},"description":"Cursor for pagination. Use the cursor value from the previous response's metadata to get the next page of results."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":250,"exclusiveMinimum":0,"description":"Pagination limit (max 250).","default":10,"title":"Limit"},"description":"Pagination limit (max 250)."},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/SortOrder"},{"type":"null"}],"description":"Order of results: 'newest' (default), 'oldest', 'longest_running', or 'most_relevant'. Sorts ads by creation date, or by longest running duration.","examples":["newest","oldest","longest_running"],"default":"newest","title":"Order"},"description":"Order of results: 'newest' (default), 'oldest', 'longest_running', or 'most_relevant'. Sorts ads by creation date, or by longest running duration."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdListResponse"}}}},"400":{"description":"Bad Request - Invalid parameters provided.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment Required - Insufficient credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Access denied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - No ads found for board.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error - Database connection issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/spyder/brands":{"get":{"tags":["Spyder"],"summary":"Get User's Spyder tracked Brands","description":"List the brands the authenticated user is tracking in Spyder. Returns brand metadata (name, domain, avatar, last-checked) — for the per-brand ad CONTENT see `/api/spyder/brand/ads`.\n\n**Credit cost:** 1 credit per call, flat-rate — regardless of how many brands you track. Empty / error responses are free.","operationId":"get_spyder_brands","parameters":[{"name":"offset","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"The offset for pagination. Use 0 for the first page, then increment by limit for subsequent pages.","default":0,"title":"Offset"},"description":"The offset for pagination. Use 0 for the first page, then increment by limit for subsequent pages."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":10,"exclusiveMinimum":0,"description":"Pagination limit (max 10). Controls the number of brands returned per request.","default":10,"title":"Limit"},"description":"Pagination limit (max 10). Controls the number of brands returned per request."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandListResponse"}}}},"400":{"description":"Bad Request - The request could not be understood or was missing required parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment Required - Insufficient credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Access denied to this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - No brands found for the given query.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error - An unexpected error occurred.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/spyder/brand":{"get":{"tags":["Spyder"],"summary":"Get Specific Spyder Brand","description":"Retrieve details for one tracked brand from the user's Spyder watchlist.\n\n**Credit cost:** 1 credit per successful lookup. Errors are free.","operationId":"get_spyder_brand","parameters":[{"name":"brand_id","in":"query","required":true,"schema":{"type":"string","description":"The ID of the brand to retrieve. User must have access to this brand.","examples":["brand_123456789","nike_official","starbucks_corp"],"title":"Brand Id"},"description":"The ID of the brand to retrieve. User must have access to this brand."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandDetailResponse"}}}},"400":{"description":"Bad Request - The request could not be understood or was missing required parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment Required - Insufficient credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - User not subscribed to this brand.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - Brand not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error - An unexpected error occurred.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/spyder/brand/ads":{"get":{"tags":["Spyder"],"summary":"Get Brand Ads in Spyder","description":"Retrieve ads for one tracked brand from the user's Spyder watchlist. Each result is a full ad object — that's why this stays per-item rather than flat-rate like the watchlist listing.\n\n**Credit cost:** 1 credit per ad returned. Empty / error responses are free.\n\n**Viewing ads on Foreplay:** Each ad in the response includes a `foreplay_url` field — the canonical platform URL for that ad, computed server-side from its internal `id`. When you reference a specific ad in a reply, render `foreplay_url` directly as a markdown link (e.g. `[See in Foreplay](<foreplay_url>)`). Do this proactively, not only when asked. NEVER invent alternate paths like `/discover/ad/{id}` — that path does not exist; only `https://app.foreplay.co/discovery?ad={id}` resolves.","operationId":"get_spyder_brand_ads","parameters":[{"name":"brand_id","in":"query","required":true,"schema":{"anyOf":[{"type":"string"},{"type":"integer"}],"description":"Brand ID to search for. User must have access to this brand.","examples":["brand_123456789","nike_official","starbucks_corp"],"title":"Brand Id"},"description":"Brand ID to search for. User must have access to this brand."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To get all ads from a specific day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59.","examples":["2024-11-12 00:00:00","2024-11-12T00:00:00","2024-11-12"],"title":"Start Date"},"description":"Start date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To get all ads from a specific day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To include all results for a given day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59.","examples":["2024-11-12 23:59:59","2024-11-13","2024-11-12T23:59:59"],"title":"End Date"},"description":"End date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To include all results for a given day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59."},{"name":"live","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/Live"},{"type":"null"}],"description":"Filter ads by live status. `true` means currently active ads, `false` means inactive ads. Leave empty to include both.","examples":["true","false"],"title":"Live"},"description":"Filter ads by live status. `true` means currently active ads, `false` means inactive ads. Leave empty to include both."},{"name":"display_format","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/DisplayFormat"}},{"type":"null"}],"description":"Filter by one or more display formats.\nAvailable formats: video, carousel, image, dco, dpa, multi_images, multi_videos, multi_medias, event, text\nExample: `?display_format=video&display_format=carousel`\n\n","examples":["video","carousel","image","dco"],"title":"Display Format"},"description":"Filter by one or more display formats.\nAvailable formats: video, carousel, image, dco, dpa, multi_images, multi_videos, multi_medias, event, text\nExample: `?display_format=video&display_format=carousel`\n\n"},{"name":"publisher_platform","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/PublisherPlatform"}},{"type":"null"}],"description":"Filter by one or more publisher platforms.\nAvailable platforms: facebook, instagram, audience_network, messenger\nExample: `?publisher_platform=facebook&publisher_platform=instagram`\n\n","examples":["facebook","instagram","facebook","audience_network"],"title":"Publisher Platform"},"description":"Filter by one or more publisher platforms.\nAvailable platforms: facebook, instagram, audience_network, messenger\nExample: `?publisher_platform=facebook&publisher_platform=instagram`\n\n"},{"name":"niches","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/Niche"}},{"type":"null"}],"description":"Filter by one or more niches.\nAvailable niches: travel, food, fashion, beauty, health, technology, automotive, finance, education, entertainment, sports, home, pets, business, other\nExample: `?niches=travel&niches=fashion`\n\n","examples":[["accessories"],["fashion"],["food/drink"],["app/software"]],"title":"Niches"},"description":"Filter by one or more niches.\nAvailable niches: travel, food, fashion, beauty, health, technology, automotive, finance, education, entertainment, sports, home, pets, business, other\nExample: `?niches=travel&niches=fashion`\n\n"},{"name":"market_target","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/MarketTarget"}},{"type":"null"}],"description":"Filter by market target.\nAvailable targets: b2b (business-to-business), b2c (business-to-consumer)\nExample: `?market_target=b2b`\n\n","examples":["b2c","b2b","b2b","b2c"],"title":"Market Target"},"description":"Filter by market target.\nAvailable targets: b2b (business-to-business), b2c (business-to-consumer)\nExample: `?market_target=b2b`\n\n"},{"name":"languages","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/Language"}},{"type":"null"}],"description":"Filter by languages.\nAccepts various language formats: 'french', 'FR', 'romanian', 'ro', 'english', 'en', etc.\nExample: `?languages=en&languages=fr`\n\n","examples":["en","es","de","fr"],"title":"Languages"},"description":"Filter by languages.\nAccepts various language formats: 'french', 'FR', 'romanian', 'ro', 'english', 'en', etc.\nExample: `?languages=en&languages=fr`\n\n"},{"name":"video_duration_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"description":"Filter ads by minimum video duration in seconds. Only applies to video ads.","examples":[5.0,10.0,30.0],"title":"Video Duration Min"},"description":"Filter ads by minimum video duration in seconds. Only applies to video ads."},{"name":"video_duration_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"description":"Filter ads by maximum video duration in seconds. Only applies to video ads.","examples":[60.0,120.0,300.0],"title":"Video Duration Max"},"description":"Filter ads by maximum video duration in seconds. Only applies to video ads."},{"name":"running_duration_min_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Filter ads by minimum running duration in days. Must be a positive integer.","examples":[1,7,30],"title":"Running Duration Min Days"},"description":"Filter ads by minimum running duration in days. Must be a positive integer."},{"name":"running_duration_max_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Filter ads by maximum running duration in days. Must be a positive integer.","examples":[7,30,90],"title":"Running Duration Max Days"},"description":"Filter ads by maximum running duration in days. Must be a positive integer."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Cursor for pagination. Use the cursor value from the previous response's metadata to get the next page of results.","examples":["eyJ0cyI6MTcwOTY1NDQwMDAwMCwiaWQiOiJhYmMxMjMifQ=="],"title":"Cursor"},"description":"Cursor for pagination. Use the cursor value from the previous response's metadata to get the next page of results."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":250,"exclusiveMinimum":0,"description":"Pagination limit (max 250). Controls the number of ads returned per request.","default":10,"title":"Limit"},"description":"Pagination limit (max 250). Controls the number of ads returned per request."},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/SortOrder"},{"type":"null"}],"description":"Order of results: 'newest' (default), 'oldest', 'longest_running', or 'most_relevant'. Sorts ads by creation date, or by longest running duration.","examples":["newest","oldest","longest_running","most_relevant"],"default":"newest","title":"Order"},"description":"Order of results: 'newest' (default), 'oldest', 'longest_running', or 'most_relevant'. Sorts ads by creation date, or by longest running duration."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdListResponse"}}}},"400":{"description":"Bad Request - Invalid parameters provided.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment Required - Insufficient credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - User not subscribed to this brand.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - No ads found for brand.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error - An unexpected error occurred.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/spyder/brand/rankings/{page_id}/top-overview":{"get":{"tags":["Spyder"],"summary":"Get Spyder Top-Ranked Ads for a Page","description":"Return the top-ranked ads for a Spyder-tracked page, comparing current ranks against a prior date. Only works for pages the authenticated user has in their Spyder watchlist.\n\n**USE THIS WHEN THE USER ASKS:** \"top ads for X\", \"best performing ads for X\", \"what's ranking for X right now\", \"which ads are up/down for X\", \"winning ads for brand X\", \"which ads climbed the ranking\". Prefer this over `get_ads_by_brand_ids` for any performance/ranking question — that endpoint returns ads in chronological/relevance order, NOT ranked by performance.\n\nEach ad in the response includes: `id` (Foreplay internal ad id), `ad_id` (Meta ad_archive_id), current `rank`, `previous_rank` from the compare window, `days_running`, a bucket `category` (e.g. `evergreen`), and the primary media URL / type.\n\n**403 handling — page not tracked:** If the response is 403 \"You are not subscribed to this page\", tell the user to add the page to their Spyder watchlist in the Foreplay app (https://app.foreplay.co/spyder → add brand). This tool only works after the user is tracking the page.\n\n**Credit cost:** 1 credit per successful call. Empty / error responses are free.","operationId":"get_spyder_page_top_overview","parameters":[{"name":"page_id","in":"path","required":true,"schema":{"type":"string","title":"Page Id"}},{"name":"date","in":"query","required":true,"schema":{"type":"string","description":"Anchor date for the ranking window (YYYY-MM-DD).","examples":["2026-08-25"],"title":"Date"},"description":"Anchor date for the ranking window (YYYY-MM-DD)."},{"name":"compareDaysAgo","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"How many days back to compare `previous_rank` against.","default":7,"title":"Comparedaysago"},"description":"How many days back to compare `previous_rank` against."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpyderTopOverviewResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment Required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - page not in the user's Spyder watchlist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Bad Gateway - upstream Foreplay server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"504":{"description":"Gateway Timeout - upstream Foreplay server timeout.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/spyder/brand/partners":{"get":{"tags":["Spyder"],"summary":"Get Spyder Related Brands (Partners) for a Page","description":"Return related pages/brands frequently seen alongside a Spyder-tracked page — useful for lookalike research and competitor mapping. Only works for pages the authenticated user has in their Spyder watchlist.\n\n**USE THIS WHEN THE USER ASKS:** \"who is similar to X\", \"related brands to X\", \"competitors of X\", \"lookalike brands for X\", \"who's advertising alongside X\", \"partner pages of X\".\n\nEach result includes: `page_id`, `page_name`, `profile_uri`, `profile_pic_url`, `categories`, `entity_type`, `like_count`, `ads_count`, `live_ads_count`, `first_seen_at`, `last_seen_at`, plus the Foreplay `brand_id` when known.\n\n**403 handling — page not tracked:** If the response is 403 \"You are not subscribed to this page\", tell the user to add the page to their Spyder watchlist in the Foreplay app (https://app.foreplay.co/spyder → add brand). This tool only works after the user is tracking the page.\n\n**Credit cost:** 1 credit per successful call. Empty / error responses are free.","operationId":"get_spyder_page_partners","parameters":[{"name":"pageId","in":"query","required":true,"schema":{"type":"string","description":"Meta page id to look up related pages/brands for.","examples":["373652569755921"],"title":"Pageid"},"description":"Meta page id to look up related pages/brands for."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"exclusiveMinimum":0,"description":"Maximum number of related pages to return.","default":5,"title":"Limit"},"description":"Maximum number of related pages to return."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpyderPartnersResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment Required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - page not in the user's Spyder watchlist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Bad Gateway - upstream Foreplay server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"504":{"description":"Gateway Timeout - upstream Foreplay server timeout.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/spyder/brand/rankings/{page_id}/list":{"get":{"tags":["Spyder"],"summary":"Get Spyder Ranked Ads (Paginated List) for a Page","description":"Return a paginated feed of ranked ads for a Spyder-tracked page, filtered by a bucket category. Only works for pages the authenticated user has in their Spyder watchlist.\n\n**USE THIS WHEN THE USER ASKS:** \"show me breakout ads for X\", \"evergreen ads for X\", \"long-running ads for X\", \"which ads are consistently performing for X\", \"newly-hot ads for X\", \"performance leaderboard for X\", or wants a PAGINATED list of ranked ads (use this over `get_spyder_page_top_overview` when they need more than the top handful, or want to filter by breakout vs evergreen).\n\n**Categories:**\n- `all`        — every ranked ad (default)\n- `breakout`   — fast-risers (large rank jump vs previous window)\n- `evergreen`  — long-running consistent performers\n\n**Response shape:** the wrapper carries `page_id`, `date`, `compare_days_ago`, `window_days`, `category`, `pagination` (`{offset, limit, total, has_more}`), `counts` (per-bucket totals for the current window), `previous_counts` (same for the compare window), and `ads` — each entry is a full ad object with a nested `ranking` sub-object (`rank`, `previous_rank`, `days_running`, `category`, `asset`, `type`, `peak_rank`, `total_ads`).\n\n**403 handling — page not tracked:** If the response is 403 \"You are not subscribed to this page\", tell the user to add the page to their Spyder watchlist in the Foreplay app (https://app.foreplay.co/spyder → add brand). This tool only works after the user is tracking the page.\n\n**Credit cost:** 1 credit per successful call. Empty / error responses are free.","operationId":"get_spyder_page_rankings_list","parameters":[{"name":"page_id","in":"path","required":true,"schema":{"type":"string","title":"Page Id"}},{"name":"category","in":"query","required":false,"schema":{"$ref":"#/components/schemas/SpyderRankingsCategory","description":"Bucket filter: `all` (default), `breakout` (fast risers), or `evergreen` (long-running consistent performers).","examples":["all","breakout","evergreen"],"default":"all"},"description":"Bucket filter: `all` (default), `breakout` (fast risers), or `evergreen` (long-running consistent performers)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"exclusiveMinimum":0,"description":"Page size (max 100).","default":20,"title":"Limit"},"description":"Page size (max 100)."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Pagination offset (number of rows to skip).","default":0,"title":"Offset"},"description":"Pagination offset (number of rows to skip)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpyderRankingsListResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment Required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - page not in the user's Spyder watchlist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Bad Gateway - upstream Foreplay server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"504":{"description":"Gateway Timeout - upstream Foreplay server timeout.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ad":{"get":{"tags":["Ad"],"summary":"Get ad details","description":"Retrieve a single ad by its public Foreplay ad id.\n\n**Credit cost:** 1 credit per successful lookup. Errors are free.\n\n**Viewing ads on Foreplay:** Each ad in the response includes a `foreplay_url` field — the canonical platform URL for that ad, computed server-side from its internal `id`. When you reference a specific ad in a reply, render `foreplay_url` directly as a markdown link (e.g. `[See in Foreplay](<foreplay_url>)`). Do this proactively, not only when asked. NEVER invent alternate paths like `/discover/ad/{id}` — that path does not exist; only `https://app.foreplay.co/discovery?ad={id}` resolves.","operationId":"get_ad_by_ad_id","parameters":[{"name":"ad_id","in":"query","required":true,"schema":{"type":"string","title":"Ad Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_AdResponse_"}}}},"400":{"description":"Bad Request - Invalid parameters provided.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment Required - Insufficient credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Access denied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - No ads found for board.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error - Database connection issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ad/{ad_id}":{"get":{"tags":["Ad"],"summary":"Get ad details by ID","description":"Retrieve a single ad by its Foreplay internal id.\n\n**Credit cost:** 1 credit per successful lookup. Errors are free.\n\n**Viewing ads on Foreplay:** Each ad in the response includes a `foreplay_url` field — the canonical platform URL for that ad, computed server-side from its internal `id`. When you reference a specific ad in a reply, render `foreplay_url` directly as a markdown link (e.g. `[See in Foreplay](<foreplay_url>)`). Do this proactively, not only when asked. NEVER invent alternate paths like `/discover/ad/{id}` — that path does not exist; only `https://app.foreplay.co/discovery?ad={id}` resolves.","operationId":"get_ad_by_id","parameters":[{"name":"ad_id","in":"path","required":true,"schema":{"type":"string","title":"Ad Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_AdResponse_"}}}},"400":{"description":"Bad Request - Invalid parameters provided.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment Required - Insufficient credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Access denied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - No ad found with the given ad_id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error - Database connection issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ad/duplicates/{ad_id}":{"get":{"tags":["Ad"],"summary":"Get group of ads using same image or video by ad ID","description":"Find every ad in the index that shares the image or video of the given ad — i.e. the same creative reused across multiple ad placements / brands.\n\n**Credit cost:** 1 credit per duplicate returned (each duplicate is a full ad object). Empty / error responses are free.\n\n**Viewing ads on Foreplay:** Each ad in the response includes a `foreplay_url` field — the canonical platform URL for that ad, computed server-side from its internal `id`. When you reference a specific ad in a reply, render `foreplay_url` directly as a markdown link (e.g. `[See in Foreplay](<foreplay_url>)`). Do this proactively, not only when asked. NEVER invent alternate paths like `/discover/ad/{id}` — that path does not exist; only `https://app.foreplay.co/discovery?ad={id}` resolves.","operationId":"get_group_duplicates_by_ad_id","parameters":[{"name":"ad_id","in":"path","required":true,"schema":{"type":"string","title":"Ad Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_AdResponse_"}}}},"400":{"description":"Bad Request - Invalid parameters provided.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment Required - Insufficient credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Access denied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - No ad found with the given ad_id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error - Database connection issues.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/brand/getAdsByBrandId":{"get":{"tags":["Brand"],"summary":"Get Ads by Brand IDs and Filters","description":"Retrieve ads for one or more brand IDs, applying the supplied filters. Each result is a full ad object.\n\n**Credit cost:** 1 credit per ad returned. Empty / error responses are free.\n\n**Empty results?** Pass `collect=true` to kick off a best-effort live fetch from Meta for the brands' Facebook pages and wait up to ~30 seconds. May still return empty if a brand has no Meta page on file, the page is restricted/deleted, or it's simply not running ads. Not perfect — use as a fallback, not a default.\n\n**Need fresher data?** `force=true` requests a live collection even when cached ads exist, waits briefly, then returns whatever is available.\n\n**Viewing ads on Foreplay:** Each ad in the response includes a `foreplay_url` field — the canonical platform URL for that ad, computed server-side from its internal `id`. When you reference a specific ad in a reply, render `foreplay_url` directly as a markdown link (e.g. `[See in Foreplay](<foreplay_url>)`). Do this proactively, not only when asked. NEVER invent alternate paths like `/discover/ad/{id}` — that path does not exist; only `https://app.foreplay.co/discovery?ad={id}` resolves.","operationId":"get_ads_by_brand_ids","parameters":[{"name":"brand_ids","in":"query","required":true,"schema":{"type":"array","items":{"type":"string"},"description":"Brand ID(s) to search for. Can be a single ID or multiple IDs separated by commas.","title":"Brand Ids"},"description":"Brand ID(s) to search for. Can be a single ID or multiple IDs separated by commas."},{"name":"live","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/Live"},{"type":"null"}],"description":"Filter ads by live status. `true` means currently active ads, `false` means inactive ads. Leave empty to include both.","examples":["true","false"],"title":"Live"},"description":"Filter ads by live status. `true` means currently active ads, `false` means inactive ads. Leave empty to include both."},{"name":"display_format","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/DisplayFormat"}},{"type":"null"}],"description":"Filter by one or more display formats.\nAvailable formats: video, carousel, image, dco, dpa, multi_images, multi_videos, multi_medias, event, text\nExample: `?display_format=video&display_format=carousel`\n\n","examples":["video","carousel","image","dco"],"title":"Display Format"},"description":"Filter by one or more display formats.\nAvailable formats: video, carousel, image, dco, dpa, multi_images, multi_videos, multi_medias, event, text\nExample: `?display_format=video&display_format=carousel`\n\n"},{"name":"publisher_platform","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/PublisherPlatform"}},{"type":"null"}],"description":"Filter by one or more publisher platforms.\nAvailable platforms: facebook, instagram, audience_network, messenger\nExample: `?publisher_platform=facebook&publisher_platform=instagram`\n\n","examples":["facebook","instagram","facebook","audience_network"],"title":"Publisher Platform"},"description":"Filter by one or more publisher platforms.\nAvailable platforms: facebook, instagram, audience_network, messenger\nExample: `?publisher_platform=facebook&publisher_platform=instagram`\n\n"},{"name":"niches","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/Niche"}},{"type":"null"}],"description":"Filter by one or more niches.\nAvailable niches: travel, food, fashion, beauty, health, technology, automotive, finance, education, entertainment, sports, home, pets, business, other\nExample: `?niches=travel&niches=fashion`\n\n","examples":[["accessories"],["fashion"],["food/drink"],["app/software"]],"title":"Niches"},"description":"Filter by one or more niches.\nAvailable niches: travel, food, fashion, beauty, health, technology, automotive, finance, education, entertainment, sports, home, pets, business, other\nExample: `?niches=travel&niches=fashion`\n\n"},{"name":"market_target","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/MarketTarget"}},{"type":"null"}],"description":"Filter by market target.\nAvailable targets: b2b (business-to-business), b2c (business-to-consumer)\nExample: `?market_target=b2b`\n\n","examples":["b2c","b2b","b2b","b2c"],"title":"Market Target"},"description":"Filter by market target.\nAvailable targets: b2b (business-to-business), b2c (business-to-consumer)\nExample: `?market_target=b2b`\n\n"},{"name":"languages","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/Language"}},{"type":"null"}],"description":"Filter by languages.\nAccepts various language formats: 'french', 'FR', 'romanian', 'ro', 'english', 'en', etc.\nExample: `?languages=en&languages=fr`\n\n","examples":["en","es","de","fr"],"title":"Languages"},"description":"Filter by languages.\nAccepts various language formats: 'french', 'FR', 'romanian', 'ro', 'english', 'en', etc.\nExample: `?languages=en&languages=fr`\n\n"},{"name":"video_duration_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"description":"Filter ads by minimum video duration in seconds. Only applies to video ads.","examples":[5.0,10.0,30.0],"title":"Video Duration Min"},"description":"Filter ads by minimum video duration in seconds. Only applies to video ads."},{"name":"video_duration_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"description":"Filter ads by maximum video duration in seconds. Only applies to video ads.","examples":[60.0,120.0,300.0],"title":"Video Duration Max"},"description":"Filter ads by maximum video duration in seconds. Only applies to video ads."},{"name":"running_duration_min_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Filter ads by minimum running duration in days. Must be a positive integer.","examples":[1,7,30],"title":"Running Duration Min Days"},"description":"Filter ads by minimum running duration in days. Must be a positive integer."},{"name":"running_duration_max_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Filter ads by maximum running duration in days. Must be a positive integer.","examples":[7,30,90],"title":"Running Duration Max Days"},"description":"Filter ads by maximum running duration in days. Must be a positive integer."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To get all ads from a specific day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59.","examples":["2024-11-12 00:00:00","2024-11-12T00:00:00","2024-11-12"],"title":"Start Date"},"description":"Start date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To get all ads from a specific day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To include all results for a given day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59.","examples":["2024-11-12 23:59:59","2024-11-13","2024-11-12T23:59:59"],"title":"End Date"},"description":"End date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To include all results for a given day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59."},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/SortOrder"},{"type":"null"}],"description":"Order of results: 'newest' (default), 'oldest', 'longest_running', or 'most_relevant'. Sorts ads by creation date, or by longest running duration.","examples":["newest","oldest","longest_running","most_relevant"],"default":"newest","title":"Order"},"description":"Order of results: 'newest' (default), 'oldest', 'longest_running', or 'most_relevant'. Sorts ads by creation date, or by longest running duration."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Cursor for pagination. Use the cursor value from the previous response's metadata to get the next page of results.","examples":["eyJ0cyI6MTcwOTY1NDQwMDAwMCwiaWQiOiJhYmMxMjMifQ=="],"title":"Cursor"},"description":"Cursor for pagination. Use the cursor value from the previous response's metadata to get the next page of results."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":250,"exclusiveMinimum":0,"description":"Pagination limit (max 250). Controls the number of ads returned per request.","default":10,"title":"Limit"},"description":"Pagination limit (max 250). Controls the number of ads returned per request."},{"name":"collect","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Best-effort live fallback. If the cached results are empty and `collect=true`, the API translates each brand id to its Meta page id (`adlibraryid`), kicks off a live fetch through our Spyder pipeline, and polls for ~30 seconds. Returns whatever is available at the end of the window — may still be empty if a brand has no Meta page on file, the page is restricted, deleted, or simply isn't running ads. Not perfect — use it as a fallback when the regular response is empty, not as a default.","default":false,"title":"Collect"},"description":"Best-effort live fallback. If the cached results are empty and `collect=true`, the API translates each brand id to its Meta page id (`adlibraryid`), kicks off a live fetch through our Spyder pipeline, and polls for ~30 seconds. Returns whatever is available at the end of the window — may still be empty if a brand has no Meta page on file, the page is restricted, deleted, or simply isn't running ads. Not perfect — use it as a fallback when the regular response is empty, not as a default.","example":["true","false"]},{"name":"force","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Force a best-effort live collection from Meta even when cached ads exist. Implies `collect`. The request waits briefly for fresh ads, then returns whatever is available.","default":false,"title":"Force"},"description":"Force a best-effort live collection from Meta even when cached ads exist. Implies `collect`. The request waits briefly for fresh ads, then returns whatever is available.","example":["true","false"]}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdListResponse"}}}},"400":{"description":"Bad Request - Invalid parameters provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment Required - Insufficient credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - No ads found for user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error - Database connection issues","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/brand/getAdsByPageId":{"get":{"tags":["Brand"],"summary":"Get Ads by Page ID and Filters","description":"Retrieve ads for a Facebook page id, applying the supplied filters. Each result is a full ad object.\n\n**Empty response? Try `collect=true`.**\nIf the default call comes back with no ads, it means we don't have cached data for this page yet. Re-issue the SAME request with `collect=true` to kick off a best-effort live fetch from Meta. The request will block for up to ~30 seconds while a background collector tries to pull the page's ads and then the endpoint returns whatever it found.\n\n**This is best-effort — it is NOT guaranteed:**\n- Collection may still return empty if the page can't be fetched (deleted, restricted, not on Meta, blocked by the ad library, region-locked, etc.).\n- The ~30s budget may run out before any ads arrive.\n- Even when ads come back, the set may be PARTIAL — only what the collector grabbed inside the window. A second call later may show more.\n- Adds significant latency. Only set `collect=true` AFTER you've already confirmed an empty response without it. Don't set it on every call.\n\n**Need fresher data?** `force=true` requests a live collection even when cached ads exist, waits briefly, then returns whatever is available.\n\n**Credit cost:** 1 credit per ad returned. Empty / error responses are free, including an empty `collect=true` response.\n\n**Viewing ads on Foreplay:** Each ad in the response includes a `foreplay_url` field — the canonical platform URL for that ad, computed server-side from its internal `id`. When you reference a specific ad in a reply, render `foreplay_url` directly as a markdown link (e.g. `[See in Foreplay](<foreplay_url>)`). Do this proactively, not only when asked. NEVER invent alternate paths like `/discover/ad/{id}` — that path does not exist; only `https://app.foreplay.co/discovery?ad={id}` resolves.","operationId":"get_brands_ads_by_page_id","parameters":[{"name":"page_id","in":"query","required":true,"schema":{"anyOf":[{"type":"string"},{"type":"integer"}],"description":"Facebook page ID to search for. This should be the numeric ID of the Facebook page.","examples":["123456789","987654321",123456789],"title":"Page Id"},"description":"Facebook page ID to search for. This should be the numeric ID of the Facebook page."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To get all ads from a specific day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59.","examples":["2024-11-12 00:00:00","2024-11-12T00:00:00","2024-11-12"],"title":"Start Date"},"description":"Start date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To get all ads from a specific day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To include all results for a given day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59.","examples":["2024-11-12 23:59:59","2024-11-13","2024-11-12T23:59:59"],"title":"End Date"},"description":"End date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To include all results for a given day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59."},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/SortOrder"},{"type":"null"}],"description":"Order of results: 'newest' (default), 'oldest', 'longest_running', or 'most_relevant'. Sorts ads by creation date, or by longest running duration.","examples":["newest","oldest","longest_running"],"default":"newest","title":"Order"},"description":"Order of results: 'newest' (default), 'oldest', 'longest_running', or 'most_relevant'. Sorts ads by creation date, or by longest running duration."},{"name":"live","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/Live"},{"type":"null"}],"description":"Filter ads by live status. `true` means currently active ads, `false` means inactive ads. Leave empty to include both.","examples":["true","false"],"title":"Live"},"description":"Filter ads by live status. `true` means currently active ads, `false` means inactive ads. Leave empty to include both."},{"name":"display_format","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/DisplayFormat"}},{"type":"null"}],"description":"Filter by one or more display formats.\nAvailable formats: video, carousel, image, dco, dpa, multi_images, multi_videos, multi_medias, event, text\nExample: `?display_format=video&display_format=carousel`\n\n","examples":["video","carousel","image","dco"],"title":"Display Format"},"description":"Filter by one or more display formats.\nAvailable formats: video, carousel, image, dco, dpa, multi_images, multi_videos, multi_medias, event, text\nExample: `?display_format=video&display_format=carousel`\n\n"},{"name":"publisher_platform","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/PublisherPlatform"}},{"type":"null"}],"description":"Filter by one or more publisher platforms.\nAvailable platforms: facebook, instagram, audience_network, messenger\nExample: `?publisher_platform=facebook&publisher_platform=instagram`\n\n","examples":["facebook","instagram","facebook","audience_network"],"title":"Publisher Platform"},"description":"Filter by one or more publisher platforms.\nAvailable platforms: facebook, instagram, audience_network, messenger\nExample: `?publisher_platform=facebook&publisher_platform=instagram`\n\n"},{"name":"niches","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/Niche"}},{"type":"null"}],"description":"Filter by one or more niches.\nAvailable niches: travel, food, fashion, beauty, health, technology, automotive, finance, education, entertainment, sports, home, pets, business, other\nExample: `?niches=travel&niches=fashion`\n\n","examples":[["accessories"],["fashion"],["food/drink"],["app/software"]],"title":"Niches"},"description":"Filter by one or more niches.\nAvailable niches: travel, food, fashion, beauty, health, technology, automotive, finance, education, entertainment, sports, home, pets, business, other\nExample: `?niches=travel&niches=fashion`\n\n"},{"name":"market_target","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/MarketTarget"}},{"type":"null"}],"description":"Filter by market target.\nAvailable targets: b2b (business-to-business), b2c (business-to-consumer)\nExample: `?market_target=b2b`\n\n","examples":["b2c","b2b","b2b","b2c"],"title":"Market Target"},"description":"Filter by market target.\nAvailable targets: b2b (business-to-business), b2c (business-to-consumer)\nExample: `?market_target=b2b`\n\n"},{"name":"languages","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/Language"}},{"type":"null"}],"description":"Filter by languages.\nAccepts various language formats: 'french', 'FR', 'romanian', 'ro', 'english', 'en', etc.\nExample: `?languages=en&languages=fr`\n\n","examples":["en","es","de","fr"],"title":"Languages"},"description":"Filter by languages.\nAccepts various language formats: 'french', 'FR', 'romanian', 'ro', 'english', 'en', etc.\nExample: `?languages=en&languages=fr`\n\n"},{"name":"video_duration_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"description":"Filter ads by minimum video duration in seconds. Only applies to video ads.","examples":[5.0,10.0,30.0],"title":"Video Duration Min"},"description":"Filter ads by minimum video duration in seconds. Only applies to video ads."},{"name":"video_duration_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"description":"Filter ads by maximum video duration in seconds. Only applies to video ads.","examples":[60.0,120.0,300.0],"title":"Video Duration Max"},"description":"Filter ads by maximum video duration in seconds. Only applies to video ads."},{"name":"running_duration_min_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Filter ads by minimum running duration in days. Must be a positive integer.","examples":[1,7,30],"title":"Running Duration Min Days"},"description":"Filter ads by minimum running duration in days. Must be a positive integer."},{"name":"running_duration_max_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Filter ads by maximum running duration in days. Must be a positive integer.","examples":[7,30,90],"title":"Running Duration Max Days"},"description":"Filter ads by maximum running duration in days. Must be a positive integer."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Cursor for pagination. Use the cursor value from the previous response's metadata to get the next page of results.","examples":["eyJ0cyI6MTcwOTY1NDQwMDAwMCwiaWQiOiJhYmMxMjMifQ=="],"title":"Cursor"},"description":"Cursor for pagination. Use the cursor value from the previous response's metadata to get the next page of results."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":250,"exclusiveMinimum":0,"description":"Pagination limit (max 250). Controls the number of ads returned per request.","default":10,"title":"Limit"},"description":"Pagination limit (max 250). Controls the number of ads returned per request."},{"name":"collect","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Best-effort live collection. Only meaningful when the default response is empty. Set to true to trigger a background fetch of this page's ads from Meta; the request blocks for up to ~30 seconds while polling, then returns whatever the collector grabbed. May still return empty, may return a partial set, and adds significant latency. Don't enable on every call — only as a follow-up after an empty response. See the endpoint description for the full caveats.","default":false,"title":"Collect"},"description":"Best-effort live collection. Only meaningful when the default response is empty. Set to true to trigger a background fetch of this page's ads from Meta; the request blocks for up to ~30 seconds while polling, then returns whatever the collector grabbed. May still return empty, may return a partial set, and adds significant latency. Don't enable on every call — only as a follow-up after an empty response. See the endpoint description for the full caveats."},{"name":"force","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Force a best-effort live collection from Meta even when cached ads exist. Implies `collect`. The request waits briefly for fresh ads, then returns whatever is available.","default":false,"title":"Force"},"description":"Force a best-effort live collection from Meta even when cached ads exist. Implies `collect`. The request waits briefly for fresh ads, then returns whatever is available."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdListResponse"}}}},"400":{"description":"Bad Request - Invalid parameters provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment Required - Insufficient credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - No ads found for user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error - Database connection issues","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/brand/getBrandsByDomain":{"get":{"tags":["Brand"],"summary":"Get Brands by Domain","description":"Find brands that advertise from a given domain. This is DISCOVERY — you don't need to already track or own the brands to find them this way.\n\n**Credit cost:** 1 credit per brand returned. Empty / error responses are free.","operationId":"get_brands_by_domain","parameters":[{"name":"domain","in":"query","required":true,"schema":{"type":"string","description":"Domain name to search for. This can be a full URL (e.g., 'https://example.com') or just the domain (e.g., 'example.com'). The system will automatically format and clean the domain. This endpoint looks up candidate brands based on the provided domain. The returned brands are potential matches and are not guaranteed to be definitively associated with the domain.","examples":["https://example.com","example.com","shop.example.com","www.example.com"],"title":"Domain"},"description":"Domain name to search for. This can be a full URL (e.g., 'https://example.com') or just the domain (e.g., 'example.com'). The system will automatically format and clean the domain. This endpoint looks up candidate brands based on the provided domain. The returned brands are potential matches and are not guaranteed to be definitively associated with the domain."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":10,"exclusiveMinimum":0,"description":"Pagination limit (max 10). Controls the number of brands returned per request.","default":10,"title":"Limit"},"description":"Pagination limit (max 10). Controls the number of brands returned per request."},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/BrandSortOrder"},{"type":"null"}],"description":"Order of results: 'most_ranked' (default) or 'least_ranked'. Sorts brands by relevance ranking.","examples":["most_ranked","least_ranked"],"default":"most_ranked","title":"Order"},"description":"Order of results: 'most_ranked' (default) or 'least_ranked'. Sorts brands by relevance ranking."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandListResponse"}}}},"400":{"description":"excluded domain","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment Required - Insufficient credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - No brands found for the given domain","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error - Database connection issues","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/brand/analytics":{"get":{"tags":["Brand"],"summary":"Get Brand Analytics by Brand ID or Page ID, gives running ads distribution and creative velocity","description":"Get analytics for a brand (running-ads distribution, creative velocity). Returns a series of analytics rows.\n\nWhen the date range includes today, today's row is computed live from the latest collected data. Pass `force=true` to request a best-effort live collection first; the request waits briefly for today's data, then returns whatever is available.\n\n**Credit cost:** 1 credit per analytics row returned. Empty / error responses are free.","operationId":"get_brands_analytics","parameters":[{"name":"id","in":"query","required":true,"schema":{"type":"string","description":"Page ID or Brand ID. Brand IDs are 20-25 character alphanumeric strings with mixed case. Page IDs are numeric Facebook page identifiers.","title":"Id"},"description":"Page ID or Brand ID. Brand IDs are 20-25 character alphanumeric strings with mixed case. Page IDs are numeric Facebook page identifiers."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To get all ads from a specific day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59.","examples":["2024-11-12 00:00:00","2024-11-12T00:00:00","2024-11-12"],"title":"Start Date"},"description":"Start date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To get all ads from a specific day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To include all results for a given day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59.","examples":["2024-11-12 23:59:59","2024-11-13","2024-11-12T23:59:59"],"title":"End Date"},"description":"End date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To include all results for a given day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59."},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/SortOrder"},{"type":"null"}],"description":"Order of results: 'newest' (default), 'oldest', 'longest_running', or 'relevance'. Sorts ads by creation date, or by longest running duration.","examples":["newest","oldest"],"default":"newest","title":"Order"},"description":"Order of results: 'newest' (default), 'oldest', 'longest_running', or 'relevance'. Sorts ads by creation date, or by longest running duration."},{"name":"force","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Force a best-effort live collection from Meta for this page. If today's data isn't available yet, the request waits briefly for it, then returns whatever is available.","examples":["true","false"],"default":false,"title":"Force"},"description":"Force a best-effort live collection from Meta for this page. If today's data isn't available yet, the request waits briefly for it, then returns whatever is available."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandAnalyticsResponse"}}}},"400":{"description":"Bad Request - Invalid parameters provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment Required - Insufficient credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - No analytics found for the given ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error - Database connection issues","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/discovery/ads":{"get":{"tags":["Discovery"],"summary":"Search and Filter Ads","description":"Search across the Foreplay ad index with text query + filters. Each result is a full ad object — image/video URLs, transcription, brand metadata, targeting, etc.\n\n**Credit cost:** 1 credit per ad returned. Empty / error responses are free.\n\n**Viewing ads on Foreplay:** Each ad in the response includes a `foreplay_url` field — the canonical platform URL for that ad, computed server-side from its internal `id`. When you reference a specific ad in a reply, render `foreplay_url` directly as a markdown link (e.g. `[See in Foreplay](<foreplay_url>)`). Do this proactively, not only when asked. NEVER invent alternate paths like `/discover/ad/{id}` — that path does not exist; only `https://app.foreplay.co/discovery?ad={id}` resolves.","operationId":"search_discovery_ads","parameters":[{"name":"query","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search text for ad name or description. Leave empty to search all ads with filters only.","examples":["foreplay.co"],"title":"Query"},"description":"Search text for ad name or description. Leave empty to search all ads with filters only."},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To get all ads from a specific day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59.","examples":["2024-11-01 00:00:00","2024-11-12T00:00:00","2024-11-12"],"title":"Start Date"},"description":"Start date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To get all ads from a specific day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To include all results for a given day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59.","examples":["2024-12-12 23:59:59","2024-11-13","2024-11-12T23:59:59"],"title":"End Date"},"description":"End date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To include all results for a given day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59."},{"name":"live","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/Live"},{"type":"null"}],"description":"Filter ads by live status. `true` means currently active ads, `false` means inactive ads. Leave empty to include both.","examples":["true","false"],"title":"Live"},"description":"Filter ads by live status. `true` means currently active ads, `false` means inactive ads. Leave empty to include both."},{"name":"display_format","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/DisplayFormat"}},{"type":"null"}],"description":"Filter by one or more display formats.\nAvailable formats: video, carousel, image, dco, dpa, multi_images, multi_videos, multi_medias, event, text\nExample: `?display_format=video&display_format=carousel`\n\n","examples":["video","carousel","image","dco"],"title":"Display Format"},"description":"Filter by one or more display formats.\nAvailable formats: video, carousel, image, dco, dpa, multi_images, multi_videos, multi_medias, event, text\nExample: `?display_format=video&display_format=carousel`\n\n"},{"name":"publisher_platform","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/PublisherPlatform"}},{"type":"null"}],"description":"Filter by one or more publisher platforms.\nAvailable platforms: facebook, instagram, audience_network, messenger\nExample: `?publisher_platform=facebook&publisher_platform=instagram`\n\n","examples":["facebook","instagram","messenger","audience_network"],"title":"Publisher Platform"},"description":"Filter by one or more publisher platforms.\nAvailable platforms: facebook, instagram, audience_network, messenger\nExample: `?publisher_platform=facebook&publisher_platform=instagram`\n\n"},{"name":"niches","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/Niche"}},{"type":"null"}],"description":"Filter by one or more niches.\nAvailable niches: accessories, app/software, beauty, business/professional, education, entertainment, fashion, food/drink, health/wellness, home/garden, jewelry/watches, other, parenting, pets, real estate, service business, medical, charity/nfp, kids/baby\nExample: `?niches=travel&niches=fashion`\n\n","examples":[["accessories"],["fashion"],["food/drink"],["app/software"]],"title":"Niches"},"description":"Filter by one or more niches.\nAvailable niches: accessories, app/software, beauty, business/professional, education, entertainment, fashion, food/drink, health/wellness, home/garden, jewelry/watches, other, parenting, pets, real estate, service business, medical, charity/nfp, kids/baby\nExample: `?niches=travel&niches=fashion`\n\n"},{"name":"market_target","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/MarketTarget"}},{"type":"null"}],"description":"Filter by market target.\nAvailable targets: b2b (business-to-business), b2c (business-to-consumer)\nExample: `?market_target=b2b`\n\n","examples":["b2c","b2b","b2b","b2c"],"title":"Market Target"},"description":"Filter by market target.\nAvailable targets: b2b (business-to-business), b2c (business-to-consumer)\nExample: `?market_target=b2b`\n\n"},{"name":"languages","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/Language"}},{"type":"null"}],"description":"Filter by languages.\nAccepts various language formats: 'french', 'FR', 'romanian', 'ro', 'english', 'en', etc.\nExample: `?languages=en&languages=fr`\n\n","examples":["en","es","de","fr"],"title":"Languages"},"description":"Filter by languages.\nAccepts various language formats: 'french', 'FR', 'romanian', 'ro', 'english', 'en', etc.\nExample: `?languages=en&languages=fr`\n\n"},{"name":"video_duration_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"description":"Filter ads by minimum video duration in seconds. Only applies to video ads.","examples":[5.0,10.0,30.0],"title":"Video Duration Min"},"description":"Filter ads by minimum video duration in seconds. Only applies to video ads."},{"name":"video_duration_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"description":"Filter ads by maximum video duration in seconds. Only applies to video ads.","examples":[60.0,120.0,300.0],"title":"Video Duration Max"},"description":"Filter ads by maximum video duration in seconds. Only applies to video ads."},{"name":"running_duration_min_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Filter ads by minimum running duration in days. Must be a positive integer.","examples":[1,7,30],"title":"Running Duration Min Days"},"description":"Filter ads by minimum running duration in days. Must be a positive integer."},{"name":"running_duration_max_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Filter ads by maximum running duration in days. Must be a positive integer.","examples":[7,30,90],"title":"Running Duration Max Days"},"description":"Filter ads by maximum running duration in days. Must be a positive integer."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Cursor for pagination. Use the cursor value from the previous response's metadata to get the next page of results.","examples":["eyJ0cyI6MTcwOTY1NDQwMDAwMCwiaWQiOiJhYmMxMjMifQ=="],"title":"Cursor"},"description":"Cursor for pagination. Use the cursor value from the previous response's metadata to get the next page of results."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":250,"exclusiveMinimum":0,"description":"Pagination limit (max 250). Controls the number of ads returned per request.","default":10,"title":"Limit"},"description":"Pagination limit (max 250). Controls the number of ads returned per request."},{"name":"order","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/SortOrder"},{"type":"null"}],"description":"Order of results: 'newest' (default), 'oldest', 'longest_running', or 'most_relevant'. Sorts ads by creation date, or by longest running duration.","examples":["newest","oldest","longest_running","most_relevant"],"default":"newest","title":"Order"},"description":"Order of results: 'newest' (default), 'oldest', 'longest_running', or 'most_relevant'. Sorts ads by creation date, or by longest running duration."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdListResponse"}}}},"400":{"description":"Bad Request - The request could not be understood or was missing required parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment Required - Insufficient credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Access denied to this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - No ads found for the given query/filters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error - An unexpected error occurred.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/discovery/brands":{"get":{"tags":["Discovery"],"summary":"Search Brands by Name","description":"Search the brand index by name (text query). Each result is a brand object — useful to resolve a brand name into the IDs needed for `/api/brand/getAdsByBrandId` etc.\n\n**Ad counts:** Every result includes `ads_count` (total ads indexed for the brand) and `active_ads_count` (currently running). Counts are computed live from the ads index — no extra credit cost.\n\n**Empty-brand filter:** By default, brands with zero indexed ads are hidden (matches the app's search behavior — reduces 'brands with no ads' noise). Pass `include_empty=true` to see them.\n\n**Ad-count filters:** `min_ads` / `max_ads` restrict results to brands within that ad-count range. Applied AFTER the search ranks by relevance, so the returned set may be smaller than `limit` when filters are set.\n\n**Credit cost:** 1 credit per brand returned. Empty / error responses are free.","operationId":"search_discovery_brands","parameters":[{"name":"query","in":"query","required":true,"schema":{"type":"string","description":"Search input for brand name. Multi-strategy match: exact phrase > name-starts-with-query > any-token-contains, each tier's results re-ranked by popularity so real brands surface above short/inactive namesakes.","examples":["nike","starbucks","apple","coca cola","microsoft"],"title":"Query"},"description":"Search input for brand name. Multi-strategy match: exact phrase > name-starts-with-query > any-token-contains, each tier's results re-ranked by popularity so real brands surface above short/inactive namesakes."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"exclusiveMinimum":0,"description":"Pagination limit (max 100). Controls the number of brands returned per request.","default":10,"title":"Limit"},"description":"Pagination limit (max 100). Controls the number of brands returned per request."},{"name":"include_empty","in":"query","required":false,"schema":{"type":"boolean","description":"Include brands with zero indexed ads. Default False — hides brand shells that have no ad data (matches the app's default search UX).","default":false,"title":"Include Empty"},"description":"Include brands with zero indexed ads. Default False — hides brand shells that have no ad data (matches the app's default search UX)."},{"name":"min_ads","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Only return brands with at least this many indexed ads.","title":"Min Ads"},"description":"Only return brands with at least this many indexed ads."},{"name":"max_ads","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"Only return brands with at most this many indexed ads.","title":"Max Ads"},"description":"Only return brands with at most this many indexed ads."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandListResponse"}}}},"400":{"description":"Bad Request - The request could not be understood or was missing required parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment Required - Insufficient credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Access denied to this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - No brands found for the given query.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error - An unexpected error occurred.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/discovery/brands/explore":{"get":{"tags":["Discovery"],"summary":"Discover Brands by Ads","description":"Find brands by characteristics of their ads (e.g. discover brands running a particular hook style or in a niche). Each result is a brand object.\n\n**Credit cost:** 1 credit per brand returned. Empty / error responses are free.","operationId":"discover_brands_by_ads","parameters":[{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To get all ads from a specific day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59.","examples":["2024-11-01 00:00:00","2024-11-12T00:00:00","2024-11-12"],"title":"Start Date"},"description":"Start date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To get all ads from a specific day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To include all results for a given day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59.","examples":["2024-12-12 23:59:59","2024-11-13","2024-11-12T23:59:59"],"title":"End Date"},"description":"End date (inclusive). Format: 'YYYY-MM-DD', 'YYYY-MM-DDTHH:MM:SS', or 'YYYY-MM-DD HH:MM:SS'. If you provide only the date (e.g. '2024-11-12'), it will be interpreted as '2024-11-12 00:00:00'. To include all results for a given day, set end_date to 'YYYY-MM-DD 23:59:59' or to the next day at '00:00:00'. Examples: start_date=2024-11-12 00:00:00, end_date=2024-11-12 23:59:59."},{"name":"live","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/Live"},{"type":"null"}],"description":"Filter ads by live status. `true` means currently active ads, `false` means inactive ads. Leave empty to include both.","examples":["true","false"],"title":"Live"},"description":"Filter ads by live status. `true` means currently active ads, `false` means inactive ads. Leave empty to include both."},{"name":"display_format","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/DisplayFormat"}},{"type":"null"}],"description":"Filter by one or more display formats.\nAvailable formats: video, carousel, image, dco, dpa, multi_images, multi_videos, multi_medias, event, text\nExample: `?display_format=video&display_format=carousel`\n\n","examples":["video","carousel","image","dco"],"title":"Display Format"},"description":"Filter by one or more display formats.\nAvailable formats: video, carousel, image, dco, dpa, multi_images, multi_videos, multi_medias, event, text\nExample: `?display_format=video&display_format=carousel`\n\n"},{"name":"publisher_platform","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/PublisherPlatform"}},{"type":"null"}],"description":"Filter by one or more publisher platforms.\nAvailable platforms: facebook, instagram, audience_network, messenger\nExample: `?publisher_platform=facebook&publisher_platform=instagram`\n\n","examples":["facebook","instagram","messenger","audience_network"],"title":"Publisher Platform"},"description":"Filter by one or more publisher platforms.\nAvailable platforms: facebook, instagram, audience_network, messenger\nExample: `?publisher_platform=facebook&publisher_platform=instagram`\n\n"},{"name":"niches","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/Niche"}},{"type":"null"}],"description":"Filter by one or more niches.\nAvailable niches: accessories, app/software, beauty, business/professional, education, entertainment, fashion, food/drink, health/wellness, home/garden, jewelry/watches, other, parenting, pets, real estate, service business, medical, charity/nfp, kids/baby\nExample: `?niches=travel&niches=fashion`\n\n","examples":[["accessories"],["fashion"],["food/drink"],["app/software"]],"title":"Niches"},"description":"Filter by one or more niches.\nAvailable niches: accessories, app/software, beauty, business/professional, education, entertainment, fashion, food/drink, health/wellness, home/garden, jewelry/watches, other, parenting, pets, real estate, service business, medical, charity/nfp, kids/baby\nExample: `?niches=travel&niches=fashion`\n\n"},{"name":"market_target","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/MarketTarget"}},{"type":"null"}],"description":"Filter by market target.\nAvailable targets: b2b (business-to-business), b2c (business-to-consumer)\nExample: `?market_target=b2b`\n\n","examples":["b2c","b2b"],"title":"Market Target"},"description":"Filter by market target.\nAvailable targets: b2b (business-to-business), b2c (business-to-consumer)\nExample: `?market_target=b2b`\n\n"},{"name":"languages","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/Language"}},{"type":"null"}],"description":"Filter by languages.\nAccepts various language formats: 'french', 'FR', 'romanian', 'ro', 'english', 'en', etc.\nExample: `?languages=en&languages=fr`\n\n","examples":["en","es","de","fr"],"title":"Languages"},"description":"Filter by languages.\nAccepts various language formats: 'french', 'FR', 'romanian', 'ro', 'english', 'en', etc.\nExample: `?languages=en&languages=fr`\n\n"},{"name":"video_duration_min","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"description":"Filter ads by minimum video duration in seconds. Only applies to video ads.","examples":[5.0,10.0,30.0],"title":"Video Duration Min"},"description":"Filter ads by minimum video duration in seconds. Only applies to video ads."},{"name":"video_duration_max","in":"query","required":false,"schema":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"description":"Filter ads by maximum video duration in seconds. Only applies to video ads.","examples":[60.0,120.0,300.0],"title":"Video Duration Max"},"description":"Filter ads by maximum video duration in seconds. Only applies to video ads."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"exclusiveMinimum":0,"description":"Pagination limit (max 10000). Controls the number of brands returned per request.","default":10,"title":"Limit"},"description":"Pagination limit (max 10000). Controls the number of brands returned per request."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrandListResponse"}}}},"400":{"description":"Bad Request - The request could not be understood or was missing required parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment Required - Insufficient credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Access denied to this resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - No brands found for the given query.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error - An unexpected error occurred.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/usage":{"get":{"tags":["Usage"],"summary":"Get user usage information","description":"Retrieve the authenticated user's account usage state — remaining credits, total credits, billing-cycle window.\n\n**Credit cost:** Free. Since it's an account-state read and is not billed.","operationId":"get_user_usage","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_UsagesResponse_"}}}},"400":{"description":"Bad Request - The request could not be understood or was missing required parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - API key is missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error - An unexpected error occurred.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/lens/reporting":{"get":{"tags":["lens"],"summary":"Get reporting data from a lens","description":"Retrieve export-style reporting data for a specific lens.\n\n**Credit cost:** 1 credit per successful call, regardless of how many rows are returned.\n\n**Required query params:** `lens_id`, `from`, `to`, `breakdown_type`, `provider`.\n\n**Breakdown types:** `'overall'`, `'country'`, `'age,gender'`, `'user_segment_key'`, `'publisher_platform,platform_position'`.\n\n**Pagination:** `page` defaults to `1`. `limit` defaults to `10` and cannot exceed `10`.\n\n**Response shape:** breakdown columns vary by `breakdown_type`, while `core_metrics`, `actions`, and `action_values` remain nested objects in each row.\n\n**Additional filters:** the public API forwards extra exact-match filters and `*_ct` / `*_nct` string filters to the lens backend as-is.","operationId":"get_lens_reporting","parameters":[{"name":"lens_id","in":"query","required":true,"schema":{"type":"string","description":"Lens ID to query.","examples":["55"],"title":"Lens Id"},"description":"Lens ID to query."},{"name":"from","in":"query","required":true,"schema":{"type":"string","description":"Start date for the report. Format: YYYY-MM-DD.","examples":["2026-06-01"],"title":"From"},"description":"Start date for the report. Format: YYYY-MM-DD."},{"name":"to","in":"query","required":true,"schema":{"type":"string","description":"End date for the report. Format: YYYY-MM-DD.","examples":["2026-06-10"],"title":"To"},"description":"End date for the report. Format: YYYY-MM-DD."},{"name":"breakdown_type","in":"query","required":true,"schema":{"type":"string","description":"Breakdown to query. Valid values: 'overall', 'country', 'age,gender', 'user_segment_key', 'publisher_platform,platform_position'.","examples":["overall","country","age,gender"],"title":"Breakdown Type"},"description":"Breakdown to query. Valid values: 'overall', 'country', 'age,gender', 'user_segment_key', 'publisher_platform,platform_position'."},{"name":"provider","in":"query","required":true,"schema":{"type":"string","description":"Advertising provider. Valid values: 'facebook' or 'tiktok'.","examples":["facebook","tiktok"],"title":"Provider"},"description":"Advertising provider. Valid values: 'facebook' or 'tiktok'."},{"name":"attr_click","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Click attribution window. Valid values: '1d', '7d', '28d'. Defaults to '7d'.","examples":["7d","1d","28d"],"title":"Attr Click"},"description":"Click attribution window. Valid values: '1d', '7d', '28d'. Defaults to '7d'."},{"name":"attr_view","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"View attribution window. Valid values: '1d', '7d', '28d'. Defaults to '1d'.","examples":["1d","7d","28d"],"title":"Attr View"},"description":"View attribution window. Valid values: '1d', '7d', '28d'. Defaults to '1d'."},{"name":"page","in":"query","required":false,"schema":{"type":"integer","exclusiveMinimum":0,"description":"1-based page number. Defaults to 1.","default":1,"title":"Page"},"description":"1-based page number. Defaults to 1."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":10,"exclusiveMinimum":0,"description":"Pagination limit (max 10). Defaults to 10.","default":10,"title":"Limit"},"description":"Pagination limit (max 10). Defaults to 10."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LensReportingResponse"}}}},"400":{"description":"Bad Request - Missing or invalid reporting parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Payment Required - Insufficient credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Access denied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error - Lens backend request failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AdListResponse":{"properties":{"metadata":{"$ref":"#/components/schemas/PaginatedMetadata"},"data":{"items":{"$ref":"#/components/schemas/AdResponse"},"type":"array","title":"Data"},"error":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Error"}},"type":"object","required":["metadata","data"],"title":"AdListResponse"},"AdResponse":{"properties":{"id":{"type":"string","title":"Id"},"ad_id":{"type":"string","title":"Ad Id"},"name":{"type":"string","title":"Name"},"brand_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brand Id"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"headline":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Headline"},"cta_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cta Title"},"categories":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Categories"},"creative_targeting":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Creative Targeting"},"languages":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Languages"},"market_target":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Market Target"},"niches":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Niches"},"product_category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Category"},"timestamped_transcription":{"anyOf":[{"items":{"anyOf":[{"type":"string"},{"$ref":"#/components/schemas/TimestampedTranscriptionModel"}]},"type":"array"},{"type":"null"}],"title":"Timestamped Transcription","default":[]},"full_transcription":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Transcription"},"cards":{"anyOf":[{"items":{"$ref":"#/components/schemas/CardModel"},"type":"array"},{"type":"null"}],"title":"Cards"},"avatar":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Avatar"},"cta_type":{"anyOf":[{"$ref":"#/components/schemas/CTAType"},{"type":"string"},{"type":"null"}],"title":"Cta Type"},"display_format":{"anyOf":[{"$ref":"#/components/schemas/DisplayFormat"},{"type":"string"},{"type":"null"}],"title":"Display Format"},"emotional_drivers":{"anyOf":[{"$ref":"#/components/schemas/EmotionalDrivers"},{"type":"null"}]},"link_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Link Url"},"live":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Live"},"persona":{"anyOf":[{"$ref":"#/components/schemas/PersonaModel"},{"type":"null"}]},"publisher_platform":{"anyOf":[{"items":{"$ref":"#/components/schemas/PublisherPlatform"},"type":"array"},{"type":"null"}],"title":"Publisher Platform"},"started_running":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Started Running"},"thumbnail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Thumbnail"},"time_product_was_mentioned":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Time Product Was Mentioned"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"},"video":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video"},"image":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image"},"content_filter":{"anyOf":[{"$ref":"#/components/schemas/ContentFilterModel"},{"type":"string"},{"type":"boolean"},{"type":"null"}],"title":"Content Filter"},"running_duration":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Running Duration"},"video_duration":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Video Duration"},"foreplay_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Foreplay Url"}},"type":"object","required":["id","ad_id","name"],"title":"AdResponse"},"BaseResponse":{"properties":{"metadata":{"anyOf":[{"$ref":"#/components/schemas/Metadata"},{"$ref":"#/components/schemas/SimpleMetadata"},{"$ref":"#/components/schemas/PaginatedMetadata"}],"title":"Metadata"},"data":{"anyOf":[{},{"items":{},"type":"array"},{"type":"null"}],"title":"Data"},"error":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Error"}},"type":"object","required":["metadata","data"],"title":"BaseResponse"},"BaseResponse_AdResponse_":{"properties":{"metadata":{"anyOf":[{"$ref":"#/components/schemas/Metadata"},{"$ref":"#/components/schemas/SimpleMetadata"},{"$ref":"#/components/schemas/PaginatedMetadata"}],"title":"Metadata"},"data":{"anyOf":[{"$ref":"#/components/schemas/AdResponse"},{"items":{"$ref":"#/components/schemas/AdResponse"},"type":"array"},{"type":"null"}],"title":"Data"},"error":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Error"}},"type":"object","required":["metadata","data"],"title":"BaseResponse[AdResponse]"},"BaseResponse_UsagesResponse_":{"properties":{"metadata":{"anyOf":[{"$ref":"#/components/schemas/Metadata"},{"$ref":"#/components/schemas/SimpleMetadata"},{"$ref":"#/components/schemas/PaginatedMetadata"}],"title":"Metadata"},"data":{"anyOf":[{"$ref":"#/components/schemas/UsagesResponse"},{"items":{"$ref":"#/components/schemas/UsagesResponse"},"type":"array"},{"type":"null"}],"title":"Data"},"error":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Error"}},"type":"object","required":["metadata","data"],"title":"BaseResponse[UsagesResponse]"},"BoardFolders":{"properties":{"folders":{"items":{"$ref":"#/components/schemas/FolderResponse"},"type":"array","title":"Folders","description":"Returned when `folders=true`. The first folder is the synthesised `Default` folder."}},"type":"object","required":["folders"],"title":"BoardFolders"},"BoardListResponse":{"properties":{"metadata":{"$ref":"#/components/schemas/SimpleMetadata"},"data":{"anyOf":[{"items":{"$ref":"#/components/schemas/BoardResponse"},"type":"array"},{"$ref":"#/components/schemas/BoardFolders"}],"title":"Data"},"error":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Error"}},"type":"object","required":["metadata","data"],"title":"BoardListResponse"},"BoardResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description"},"created_at":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Created At"}},"type":"object","required":["id","name","description"],"title":"BoardResponse"},"Body_ensure_api_key_for_oauth_user_api_mcp_oauth_utility_ensure_api_key_post":{"properties":{"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id","description":"Foreplay user id to provision."},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Email to provision (alternative to user_id)."}},"type":"object","title":"Body_ensure_api_key_for_oauth_user_api_mcp_oauth_utility_ensure_api_key_post"},"BrandAnalyticsResponse":{"properties":{"metadata":{"$ref":"#/components/schemas/PaginatedMetadata"},"data":{"items":{"$ref":"#/components/schemas/BrandAnalyticsRow"},"type":"array","title":"Data"},"error":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Error"}},"type":"object","required":["metadata","data"],"title":"BrandAnalyticsResponse"},"BrandAnalyticsRow":{"properties":{"date":{"type":"string","title":"Date","description":"Day, YYYY-MM-DD (UTC)."},"active_count":{"type":"integer","title":"Active Count","description":"Ads active on that day."},"inactive_count":{"type":"integer","title":"Inactive Count","description":"Ads inactive on that day."},"carousel":{"type":"integer","title":"Carousel","description":"Active carousel ads."},"dco":{"type":"integer","title":"Dco","description":"Active dco ads."},"dpa":{"type":"integer","title":"Dpa","description":"Active dpa ads."},"event":{"type":"integer","title":"Event","description":"Active event ads."},"image":{"type":"integer","title":"Image","description":"Active image ads."},"multi_images":{"type":"integer","title":"Multi Images","description":"Active multi_images ads."},"multi_medias":{"type":"integer","title":"Multi Medias","description":"Active multi_medias ads."},"multi_videos":{"type":"integer","title":"Multi Videos","description":"Active multi_videos ads."},"page_like":{"type":"integer","title":"Page Like","description":"Active page_like ads."},"text":{"type":"integer","title":"Text","description":"Active text ads."},"video":{"type":"integer","title":"Video","description":"Active video ads."}},"type":"object","required":["date","active_count","inactive_count","carousel","dco","dpa","event","image","multi_images","multi_medias","multi_videos","page_like","text","video"],"title":"BrandAnalyticsRow","description":"One day of analytics for a page. Format counts are over active ads."},"BrandDetailResponse":{"properties":{"metadata":{"$ref":"#/components/schemas/SimpleMetadata"},"data":{"anyOf":[{"$ref":"#/components/schemas/BrandResponse"},{"items":{},"type":"array","maxItems":0}],"title":"Data"},"error":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Error"}},"type":"object","required":["metadata","data"],"title":"BrandDetailResponse"},"BrandListResponse":{"properties":{"metadata":{"$ref":"#/components/schemas/PaginatedMetadata"},"data":{"items":{"$ref":"#/components/schemas/BrandResponse"},"type":"array","title":"Data"},"error":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Error"}},"type":"object","required":["metadata","data"],"title":"BrandListResponse"},"BrandResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"$ref":"#/components/schemas/Description"},{"type":"null"}],"default":{"text":""}},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"niches":{"items":{"type":"string"},"type":"array","title":"Niches","default":[]},"verification_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verification Status"},"url":{"anyOf":[{"type":"string","maxLength":2083,"minLength":1,"format":"uri"},{"type":"null"}],"title":"Url"},"websites":{"items":{"type":"string","maxLength":2083,"minLength":1,"format":"uri"},"type":"array","title":"Websites","default":[]},"avatar":{"anyOf":[{"type":"string","maxLength":2083,"minLength":1,"format":"uri"},{"type":"null"}],"title":"Avatar"},"ad_library_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ad Library Id"},"is_delegate_page_with_linked_primary_profile":{"type":"boolean","title":"Is Delegate Page With Linked Primary Profile","default":false},"ads_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ads Count"},"active_ads_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Active Ads Count"}},"type":"object","required":["id","name"],"title":"BrandResponse"},"BrandSortOrder":{"type":"string","enum":["most_ranked","least_ranked"],"title":"BrandSortOrder"},"CTAType":{"type":"string","enum":["SHOP_NOW","SUBSCRIBE"],"title":"CTAType"},"CardModel":{"properties":{"cta_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cta Text"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"headline":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Headline"},"image":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image"},"video":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video"},"link_description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Link Description"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type"},"timestamped_transcription":{"anyOf":[{"items":{"anyOf":[{"type":"string"},{"$ref":"#/components/schemas/TimestampedTranscriptionModel"}]},"type":"array"},{"type":"null"}],"title":"Timestamped Transcription","default":[]},"full_transcription":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Transcription"},"video_duration":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Video Duration"}},"type":"object","title":"CardModel"},"ContentFilterModel":{"properties":{"Facts_and_Stats":{"type":"number","title":"Facts And Stats","default":0.0},"Features_and_Benefits":{"type":"number","title":"Features And Benefits","default":0.0},"Promotion_and_Discount":{"type":"number","title":"Promotion And Discount","default":0.0},"Testimonial_Review":{"type":"number","title":"Testimonial Review","default":0.0},"other":{"type":"number","title":"Other","default":0.0},"UGC":{"type":"number","title":"Ugc","default":0.0},"Us_vs_Them":{"type":"number","title":"Us Vs Them","default":0.0},"Before_and_After":{"type":"number","title":"Before And After","default":0.0},"Podcast":{"type":"number","title":"Podcast","default":0.0},"Reasons_why":{"type":"number","title":"Reasons Why","default":0.0},"Media_and_Press":{"type":"number","title":"Media And Press","default":0.0},"Unboxing":{"type":"number","title":"Unboxing","default":0.0},"Green_Screen":{"type":"number","title":"Green Screen","default":0.0},"Holiday_Seasonal":{"type":"number","title":"Holiday Seasonal","default":0.0}},"type":"object","title":"ContentFilterModel"},"DateAggregation":{"type":"string","enum":["day","month","year"],"title":"DateAggregation"},"Description":{"properties":{"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text","default":""}},"type":"object","title":"Description"},"DisplayFormat":{"type":"string","enum":["carousel","dco","dpa","event","image","multi_images","multi_medias","multi_videos","page_like","text","video"],"title":"DisplayFormat"},"EmotionalDrivers":{"properties":{"achievement":{"type":"integer","title":"Achievement","default":1},"anger":{"type":"integer","title":"Anger","default":1},"authority":{"type":"integer","title":"Authority","default":1},"belonging":{"type":"integer","title":"Belonging","default":1},"competence":{"type":"integer","title":"Competence","default":1},"curiosity":{"type":"integer","title":"Curiosity","default":1},"empowerment":{"type":"integer","title":"Empowerment","default":1},"engagement":{"type":"integer","title":"Engagement","default":1},"esteem":{"type":"integer","title":"Esteem","default":1},"fear":{"type":"integer","title":"Fear","default":1},"guilt":{"type":"integer","title":"Guilt","default":1},"nostalgia":{"type":"integer","title":"Nostalgia","default":1},"nurturance":{"type":"integer","title":"Nurturance","default":1},"security":{"type":"integer","title":"Security","default":1},"urgency":{"type":"integer","title":"Urgency","default":1}},"type":"object","title":"EmotionalDrivers"},"ErrorCode":{"type":"integer","enum":[200,400,401,402,403,404,405,406,422,500],"title":"ErrorCode"},"ErrorResponse":{"properties":{"metadata":{"anyOf":[{"$ref":"#/components/schemas/Metadata"},{"$ref":"#/components/schemas/SimpleMetadata"},{"$ref":"#/components/schemas/PaginatedMetadata"}],"title":"Metadata"},"error":{"type":"object","title":"Error"},"data":{"anyOf":[{},{"type":"null"}],"title":"Data","default":[]}},"type":"object","required":["metadata","error"],"title":"ErrorResponse"},"FolderResponse":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"is_default":{"type":"boolean","title":"Is Default","default":false},"created_at":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Created At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"},"team_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Team Id"},"boards":{"items":{"$ref":"#/components/schemas/BoardResponse"},"type":"array","title":"Boards","default":[]}},"type":"object","title":"FolderResponse","description":"A folder node in the nested-tree response from GET /api/boards.\n\nReturned ONLY when the caller passes ?folders=true. Each folder carries\nits full set of boards inline (already resolved server-side) — the\nconsumer never needs to do an id → BoardResponse join.\n\nNo `id` is exposed: consumers identify folders by `name` (or by the\n`is_default` flag for the synthesised default folder). The Firestore\ndocument id is intentionally not surfaced because no endpoint consumes\nit from the caller — boards inside still have their own ids.\n\nA board can technically belong to multiple folders (the Firestore\nrelationship lives on the folder via `boardIds`, an array). When that\nhappens the board appears under each folder's `boards` array. The\nserver does NOT dedupe — the tree is rendered as the data models it.\n\n`is_default = true` marks the synthesised folder that holds boards\nnot assigned to any user-created folder. It always sits at the top\nof the `folders` array. All other folders are sorted alphabetically\nby name, case-insensitive.\n\n`team_id` is populated for team-shared folders, null/absent for personal\nones — same dual-mode as the boards endpoint itself.","example":{"boards":[{"created_at":1748000000,"description":"","id":"4m2mgyX7RAXgyj4VXyQh","name":"Hooks library"}],"created_at":1749042152,"created_by":"9OjeLifnRAZjoFkgLjx9U2soQxW2","is_default":false,"name":"PJ's Inspo","team_id":"OpkvN9WFzeyTF4cuXvKd"}},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"InspectTokenRequest":{"properties":{"access_token":{"type":"string","title":"Access Token"}},"type":"object","required":["access_token"],"title":"InspectTokenRequest"},"Language":{"type":"string","enum":["Afrikaans","Albanian","Arabic","Armenian","Azerbaijani","Bambara","Basque","Bengali","Bosnian","Bulgarian","Burmese","Catalan, Valencian","Central Khmer","Chichewa, Chewa, Nyanja","Corsican","Croatian","Czech","Danish","Dutch, Flemish","English","Estonian","Finnish","French","Galician","Georgian","German","Greek (modern)","Guaraní","Hausa","Hebrew (modern)","Hindi","Hungarian","Icelandic","Indonesian","Italian","Japanese","Javanese","Kannada","Kazakh","Korean","Latin","Latvian","Lingala","Lithuanian","Luxembourgish, Letzeburgesch","Macedonian","Malagasy","Malay","Malayalam","Maltese","Maori","Marathi","Mongolian","Norwegian","Oriya","Oromo","Persian","Polish","Portuguese","Romanian, Moldavian, Moldovan","Russian","Serbian","Shona","Sinhala, Sinhalese","Slovak","Slovene","Spanish, Castilian","Sundanese","Swedish","Telugu","Thai","Turkish","Turkmen","Ukrainian","Urdu","Uzbek","Vietnamese","Welsh","Western Frisian"],"title":"Language"},"LensReportingMetadata":{"properties":{"success":{"type":"boolean","title":"Success","default":true},"message":{"type":"string","title":"Message","default":"Your request has been processed successfully."},"status_code":{"type":"integer","title":"Status Code","default":200},"processed_at":{"type":"integer","title":"Processed At"},"count":{"type":"integer","title":"Count","description":"Total rows across all pages."},"pages":{"type":"integer","title":"Pages","description":"Total pages for the current `limit`."},"filters":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Filters"}},"type":"object","required":["count","pages"],"title":"LensReportingMetadata"},"LensReportingResponse":{"properties":{"metadata":{"$ref":"#/components/schemas/LensReportingMetadata"},"data":{"items":{"$ref":"#/components/schemas/LensReportingRow"},"type":"array","title":"Data"},"error":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Error"}},"type":"object","required":["metadata","data"],"title":"LensReportingResponse"},"LensReportingRow":{"properties":{"core_metrics":{"type":"object","title":"Core Metrics"},"actions":{"type":"object","title":"Actions"},"action_values":{"type":"object","title":"Action Values"}},"additionalProperties":true,"type":"object","title":"LensReportingRow","description":"Breakdown columns depend on `breakdown_type` and appear as extra keys."},"LimitedUserResponse":{"properties":{"id":{"type":"string","title":"Id"},"email":{"type":"string","title":"Email"}},"type":"object","required":["id","email"],"title":"LimitedUserResponse"},"LinkKeycloakUserRequest":{"properties":{"foreplay_user_id":{"type":"string","title":"Foreplay User Id","description":"Foreplay MySQL users.id"},"keycloak_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Keycloak User Id","description":"Keycloak user UUID"},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username","description":"Keycloak username"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Keycloak email"}},"type":"object","required":["foreplay_user_id"],"title":"LinkKeycloakUserRequest"},"Live":{"type":"string","enum":["true","false"],"title":"Live"},"MarketTarget":{"type":"string","enum":["b2b","b2c"],"title":"MarketTarget"},"Metadata":{"properties":{"success":{"type":"boolean","title":"Success","default":true},"message":{"type":"string","title":"Message","default":"Your request has been processed successfully."},"status_code":{"type":"integer","title":"Status Code","default":200},"processed_at":{"type":"integer","title":"Processed At"},"cursor":{"anyOf":[{"type":"integer"},{"type":"string"},{"type":"null"}],"title":"Cursor"},"filters":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Filters"},"order":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Order"},"count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Count"}},"type":"object","title":"Metadata"},"Niche":{"type":"string","enum":["accessories","app/software","beauty","business/professional","education","entertainment","fashion","food/drink","health/wellness","home/garden","jewelry/watches","other","parenting","pets","real estate","service business","medical","charity/nfp","kids/baby"],"title":"Niche"},"OrderBy":{"type":"string","enum":["Timestamp","LatencyMs","ResponseStatus","CreditsUsed"],"title":"OrderBy"},"PaginatedMetadata":{"properties":{"success":{"type":"boolean","title":"Success","default":true},"message":{"type":"string","title":"Message","default":"Your request has been processed successfully."},"status_code":{"type":"integer","title":"Status Code","default":200},"processed_at":{"type":"integer","title":"Processed At"},"cursor":{"anyOf":[{"type":"integer"},{"type":"string"},{"type":"null"}],"title":"Cursor"},"filters":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Filters"},"order":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Order"},"count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Count"}},"type":"object","title":"PaginatedMetadata"},"PersonaModel":{"properties":{"age":{"type":"string","title":"Age","default":"unknown"},"gender":{"type":"string","title":"Gender","default":"unknown"}},"type":"object","title":"PersonaModel"},"PublisherPlatform":{"type":"string","enum":["facebook","instagram","audience_network","messenger","tiktok","youtube","linkedin","threads","whatsapp"],"title":"PublisherPlatform"},"RevokeKeycloakUserRequest":{"properties":{"foreplay_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Foreplay User Id","description":"Foreplay MySQL users.id"},"keycloak_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Keycloak User Id","description":"Keycloak user UUID"},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username","description":"Keycloak username"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Keycloak email"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id","description":"OAuth client to revoke, for example claude-mcp or chatgpt-mcp"},"logout_sessions":{"type":"boolean","title":"Logout Sessions","description":"Invalidate active Keycloak sessions","default":true},"remove_client_consent":{"type":"boolean","title":"Remove Client Consent","description":"Remove consent for the MCP client","default":true}},"type":"object","title":"RevokeKeycloakUserRequest"},"SimpleMetadata":{"properties":{"success":{"type":"boolean","title":"Success","default":true},"message":{"type":"string","title":"Message","default":"Your request has been processed successfully."},"status_code":{"type":"integer","title":"Status Code","default":200},"processed_at":{"type":"integer","title":"Processed At"}},"type":"object","title":"SimpleMetadata"},"SortOrder":{"type":"string","enum":["newest","oldest","longest_running","most_relevant"],"title":"SortOrder"},"SpyderPartnerBrand":{"properties":{"pageId":{"type":"string","title":"Pageid"},"pageName":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pagename"},"currentPageName":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currentpagename"},"profileUri":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Profileuri"},"profilePicUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Profilepicurl"},"categories":{"items":{"type":"string"},"type":"array","title":"Categories"},"entityType":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entitytype"},"likeCount":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Likecount"},"isDeleted":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Isdeleted"},"adsCount":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Adscount"},"brandId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brandid"},"lastAdArchiveId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lastadarchiveid"},"firstSeenAt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Firstseenat"},"lastSeenAt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lastseenat"},"liveAdsCount":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Liveadscount"}},"additionalProperties":true,"type":"object","required":["pageId"],"title":"SpyderPartnerBrand"},"SpyderPartnersData":{"properties":{"results":{"items":{"$ref":"#/components/schemas/SpyderPartnerBrand"},"type":"array","title":"Results"}},"additionalProperties":true,"type":"object","title":"SpyderPartnersData"},"SpyderPartnersResponse":{"properties":{"metadata":{"$ref":"#/components/schemas/SimpleMetadata"},"data":{"anyOf":[{"$ref":"#/components/schemas/SpyderPartnersData"},{"items":{},"type":"array","maxItems":0}],"title":"Data"},"error":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Error"}},"type":"object","required":["metadata","data"],"title":"SpyderPartnersResponse"},"SpyderRankedAd":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Foreplay internal ad id, if the ad is indexed."},"ad_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ad Id","description":"Meta ad_archive_id."},"rank":{"type":"integer","title":"Rank","description":"Current rank in the window."},"previous_rank":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Previous Rank","description":"Rank in the compare window (compareDaysAgo)."},"days_running":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Days Running","description":"Total days this ad has been running."},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"Bucket label the ranker assigned (e.g. `evergreen`, `newcomer`)."},"asset":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asset","description":"URL to the ad's primary media (image or video)."},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type","description":"Media type: `image` or `video`."}},"additionalProperties":true,"type":"object","required":["rank"],"title":"SpyderRankedAd"},"SpyderRankingsCategory":{"type":"string","enum":["all","breakout","evergreen"],"title":"SpyderRankingsCategory","description":"Bucket filter for the ranked-ads list. `all` returns every\nranked ad, `breakout` filters to fast-risers, `evergreen` to\nlong-running consistent performers."},"SpyderRankingsCategoryCounts":{"properties":{"all":{"type":"integer","title":"All","default":0},"breakout":{"type":"integer","title":"Breakout","default":0},"evergreen":{"type":"integer","title":"Evergreen","default":0}},"additionalProperties":true,"type":"object","title":"SpyderRankingsCategoryCounts","description":"Counts per bucket: `all` = all ranked ads, `breakout` = fast\nrisers, `evergreen` = long-running consistent performers."},"SpyderRankingsListData":{"properties":{"pageId":{"type":"string","title":"Pageid"},"date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Date"},"compareDaysAgo":{"type":"integer","title":"Comparedaysago","default":7},"windowDays":{"type":"integer","title":"Windowdays","default":14},"category":{"type":"string","title":"Category","default":"all"},"ads":{"items":{"type":"object"},"type":"array","title":"Ads"},"pagination":{"$ref":"#/components/schemas/SpyderRankingsPagination"},"counts":{"$ref":"#/components/schemas/SpyderRankingsCategoryCounts"},"previousCounts":{"$ref":"#/components/schemas/SpyderRankingsCategoryCounts"}},"additionalProperties":true,"type":"object","required":["pageId"],"title":"SpyderRankingsListData","description":"Response wrapper for `/brands/private/rankings/{page_id}/list`.\n\n`ads` is passthrough — each entry is the full upstream ad object\n(including a nested `ranking` sub-object with `rank`,\n`previous_rank`, `days_running`, `category`, `asset`, `type`,\n`peak_rank`, `total_ads`). We don't re-shape it because the\nupstream ad schema is broad and evolves; consumers get the raw\nfields without us having to keep a mirror model in sync."},"SpyderRankingsListResponse":{"properties":{"metadata":{"$ref":"#/components/schemas/SimpleMetadata"},"data":{"anyOf":[{"$ref":"#/components/schemas/SpyderRankingsListData"},{"items":{},"type":"array","maxItems":0}],"title":"Data"},"error":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Error"}},"type":"object","required":["metadata","data"],"title":"SpyderRankingsListResponse"},"SpyderRankingsPagination":{"properties":{"offset":{"type":"integer","title":"Offset","default":0},"limit":{"type":"integer","title":"Limit","default":0},"total":{"type":"integer","title":"Total","default":0},"hasMore":{"type":"boolean","title":"Hasmore","default":false}},"additionalProperties":true,"type":"object","title":"SpyderRankingsPagination"},"SpyderTopOverviewData":{"properties":{"pageId":{"type":"string","title":"Pageid"},"date":{"type":"string","title":"Date","description":"Anchor date, YYYY-MM-DD."},"compareDaysAgo":{"type":"integer","title":"Comparedaysago"},"ads":{"items":{"$ref":"#/components/schemas/SpyderRankedAd"},"type":"array","title":"Ads"}},"additionalProperties":true,"type":"object","required":["pageId","date","compareDaysAgo"],"title":"SpyderTopOverviewData"},"SpyderTopOverviewResponse":{"properties":{"metadata":{"$ref":"#/components/schemas/SimpleMetadata"},"data":{"anyOf":[{"$ref":"#/components/schemas/SpyderTopOverviewData"},{"items":{},"type":"array","maxItems":0}],"title":"Data"},"error":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Error"}},"type":"object","required":["metadata","data"],"title":"SpyderTopOverviewResponse"},"SwipefileSortOrder":{"type":"string","enum":["saved_newest","newest","oldest","longest_running","most_relevant"],"title":"SwipefileSortOrder"},"TimestampedTranscriptionModel":{"properties":{"startTime":{"type":"number","title":"Starttime"},"endTime":{"type":"number","title":"Endtime"},"sentence":{"type":"string","title":"Sentence"}},"type":"object","required":["startTime","endTime","sentence"],"title":"TimestampedTranscriptionModel"},"UsagesResponse":{"properties":{"start_date":{"type":"string","format":"date-time","title":"Start Date"},"end_date":{"type":"string","format":"date-time","title":"End Date"},"total_credits":{"type":"integer","title":"Total Credits"},"remaining_credits":{"type":"integer","title":"Remaining Credits"},"user":{"$ref":"#/components/schemas/LimitedUserResponse"}},"type":"object","required":["start_date","end_date","total_credits","remaining_credits","user"],"title":"UsagesResponse"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer"}}},"security":[{"BearerAuth":[]}]}