{"openapi":"3.0.3","info":{"title":"GetHookdAI API Documentation","description":"All endpoints require a Public API team token passed as `Authorization: Bearer <token>`. Each endpoint additionally requires the token to carry the relevant scope (shown in each operation summary, e.g. `explore:read`, `boards:write`).","version":"1.4.0"},"servers":[{"url":"https://app.gethookd.ai"}],"security":[{"bearerAuth":[]}],"paths":{"/api/v1/authcheck":{"get":{"summary":"Validate Public API token and return workspace info.\n\nVerifies the provided team API token and returns the authenticated workspace and token scopes.","operationId":"validatePublicAPITokenAndReturnWorkspaceInfoVerifiesTheProvidedTeamAPITokenAndReturnsTheAuthenticatedWorkspaceAndTokenScopes","description":"","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":false,"data":{"authenticated":true,"workspace":{"id":123,"name":"Acme Team"},"scopes":["explore:read"]}},"properties":{"errors":{"type":"boolean","example":false},"data":{"type":"object","properties":{"authenticated":{"type":"boolean","example":true},"workspace":{"type":"object","properties":{"id":{"type":"integer","example":123},"name":{"type":"string","example":"Acme Team"}}},"scopes":{"type":"array","example":["explore:read"],"items":{"type":"string"}}}}}}}}},"401":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Invalid or expired token."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Invalid or expired token."}}}}}}},"tags":["Public API v1 — Auth"]}},"/api/v1/page-types":{"get":{"summary":"List active landing-page types.\n\nReturns the catalogue of active landing-page types that may be passed (by slug) to the `page_type` filter on GET /api/v1/explore.","operationId":"listActiveLandingPageTypes","description":"","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":false,"data":[{"id":7,"slug":"quiz_page","title":"Quiz Page"},{"id":6,"slug":"vsl_page","title":"VSL Page"}]},"properties":{"errors":{"type":"boolean","example":false},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":7},"slug":{"type":"string","example":"quiz_page"},"title":{"type":"string","example":"Quiz Page"}}}}}}}}},"401":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Invalid or expired token."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Invalid or expired token."}}}}}}},"tags":["Public API v1 — Explore"]}},"/api/v1/explore":{"get":{"summary":"List ads with filtering and pagination.\n\nRequires a Public API token with scope: explore:read.","operationId":"listAdsWithFilteringAndPaginationRequiresAPublicAPITokenWithScopeExploreread","description":"","parameters":[{"in":"query","name":"page","description":"The page number.","example":1,"required":false,"schema":{"type":"integer","description":"The page number.","example":1}},{"in":"query","name":"per_page","description":"Number of items per page (1-100). Defaults to 20. Values outside 1-100 are rejected with HTTP 422.","example":24,"required":false,"schema":{"type":"integer","description":"Number of items per page (1-100). Defaults to 20. Values outside 1-100 are rejected with HTTP 422.","minimum":1,"maximum":100,"example":24}},{"in":"query","name":"query","description":"Free-text search query.","example":"supplements","required":false,"schema":{"type":"string","description":"Free-text search query.","example":"supplements"}},{"in":"query","name":"sort_column","description":"Sort column. One of: created_at, start_date, days_active, used_count, popularity.","example":"created_at","required":false,"schema":{"type":"string","enum":["created_at","start_date","days_active","used_count","popularity"],"description":"Sort column. One of: created_at, start_date, days_active, used_count, popularity.","example":"created_at"}},{"in":"query","name":"sort_direction","description":"Sort direction. One of: asc, desc.","example":"desc","required":false,"schema":{"type":"string","description":"Sort direction. One of: asc, desc.","example":"desc"}},{"in":"query","name":"start-date","description":"date Start date (YYYY-MM-DD). Requires end-date.","example":"2025-01-01","required":false,"schema":{"type":"string","description":"date Start date (YYYY-MM-DD). Requires end-date.","example":"2025-01-01"}},{"in":"query","name":"end-date","description":"date End date (must be >= start-date).","example":"2025-01-31","required":false,"schema":{"type":"string","description":"date End date (must be >= start-date).","example":"2025-01-31"}},{"in":"query","name":"status","description":"Filter by library status. One of: active, inactive.","example":"active","required":false,"schema":{"type":"string","description":"Filter by library status. One of: active, inactive.","example":"active"}},{"in":"query","name":"ad-format","description":"Display formats (CSV). Allowed: image, video, carousels, multi_images, multi_videos, dcos, dpas, events, page_likes, multi_medias.","example":"image,video","required":false,"schema":{"type":"string","description":"Display formats (CSV). Allowed: image, video, carousels, multi_images, multi_videos, dcos, dpas, events, page_likes, multi_medias.","example":"image,video"}},{"in":"query","name":"run-time","description":"Minimum days active.","example":7,"required":false,"schema":{"type":"integer","description":"Minimum days active.","example":7}},{"in":"query","name":"language","description":"Languages (CSV). Capitalized 2-letter ISO 639-1 code (Except: ZHT - Chinese Traditional, CEB - Cebuano, HMN - Hmong).","example":"EN,ES","required":false,"schema":{"type":"string","description":"Languages (CSV). Capitalized 2-letter ISO 639-1 code (Except: ZHT - Chinese Traditional, CEB - Cebuano, HMN - Hmong).","example":"EN,ES"}},{"in":"query","name":"platform","description":"Platforms (CSV).","example":"facebook,instagram","required":false,"schema":{"type":"string","description":"Platforms (CSV).","example":"facebook,instagram"}},{"in":"query","name":"niche","description":"Niches (CSV). Valid IDs: 1=Accessories, 2=Alcohol, 3=App/Software, 4=Automotive, 5=Beauty, 6=Book/Publishing, 7=Business/Professional, 8=Charity/NFP, 9=Info, 10=Entertainment, 11=Fashion, 12=Finance, 13=Food/Drink, 14=Games, 15=Government, 16=Health/Wellness, 17=Home/Garden, 18=Insurance, 19=Jewelry/Watches, 20=Kids/Baby, 21=Media/News, 22=Medical, 23=Pets, 24=Real Estate, 25=Service Business, 26=Sports/Outdoors, 27=Tech, 28=Travel, 29=Other, 30=Supplements.","example":"5,11,16","required":false,"schema":{"type":"string","description":"Niches (CSV). Valid IDs: 1=Accessories, 2=Alcohol, 3=App/Software, 4=Automotive, 5=Beauty, 6=Book/Publishing, 7=Business/Professional, 8=Charity/NFP, 9=Info, 10=Entertainment, 11=Fashion, 12=Finance, 13=Food/Drink, 14=Games, 15=Government, 16=Health/Wellness, 17=Home/Garden, 18=Insurance, 19=Jewelry/Watches, 20=Kids/Baby, 21=Media/News, 22=Medical, 23=Pets, 24=Real Estate, 25=Service Business, 26=Sports/Outdoors, 27=Tech, 28=Travel, 29=Other, 30=Supplements.","example":"5,11,16"}},{"in":"query","name":"page_type","description":"Landing-page types (CSV slugs, e.g. quiz_page, advertorial, vsl_page). Each slug must be a recognised active type; discover the full active slug list at GET /api/v1/page-types.","example":"quiz_page,vsl_page","required":false,"schema":{"type":"string","description":"Landing-page types (CSV slugs, e.g. quiz_page, advertorial, vsl_page). Each slug must be a recognised active type; discover the full active slug list at GET /api/v1/page-types.","example":"quiz_page,vsl_page"}},{"in":"query","name":"performance_scores","description":"Performance score buckets (CSV). One or more of: testing, scaling, growing, optimized, winning.","example":"optimized,winning","required":false,"schema":{"type":"string","description":"Performance score buckets (CSV). One or more of: testing, scaling, growing, optimized, winning.","example":"optimized,winning"}},{"in":"query","name":"used_count","description":"Creative usage. Minimum number of times used.","example":3,"required":false,"schema":{"type":"integer","description":"Creative usage. Minimum number of times used.","example":3}},{"in":"query","name":"video_lengths","description":"Video length filter (CSV). One or more of: less_than_1_min, 1_to_3_min, 3_to_5_min, more_than_5_min.","example":"1_to_3_min","required":false,"schema":{"type":"string","description":"Video length filter (CSV). One or more of: less_than_1_min, 1_to_3_min, 3_to_5_min, more_than_5_min.","example":"1_to_3_min"}},{"in":"query","name":"eu_transparency","description":"EU transparency flag. 0 or 1.","example":1,"required":false,"schema":{"type":"integer","description":"EU transparency flag. 0 or 1.","example":1}},{"in":"query","name":"eu_total_reach","description":"Minimum EU total reach. Filters ads with eu_total_reach >= value.","example":200,"required":false,"schema":{"type":"integer","description":"Minimum EU total reach. Filters ads with eu_total_reach >= value.","example":200}},{"in":"query","name":"gender_audience","description":"Gender audience filter (CSV). One or more of: all, men, women.","example":"men,women","required":false,"schema":{"type":"string","description":"Gender audience filter (CSV). One or more of: all, men, women.","example":"men,women"}},{"in":"query","name":"age_audience","description":"Age audience filter (CSV). One or more of: 13-17, 18-24, 25-34, 35-44, 45-54, 55-64, 65.","example":"25-34,35-44","required":false,"schema":{"type":"string","description":"Age audience filter (CSV). One or more of: 13-17, 18-24, 25-34, 35-44, 45-54, 55-64, 65.","example":"25-34,35-44"}},{"in":"query","name":"location","description":"Capitalized 2-letter country codes (CSV). Use \"all\" to not filter.","example":"US,DE","required":false,"schema":{"type":"string","description":"Capitalized 2-letter country codes (CSV). Use \"all\" to not filter.","example":"US,DE"}},{"in":"query","name":"ad_spend_range","description":"Spend buckets (CSV, keys 1..6).","example":"3,4","required":false,"schema":{"type":"string","description":"Spend buckets (CSV, keys 1..6).","example":"3,4"}},{"in":"query","name":"excluded_brands","description":"Exclude brand IDs (CSV).","example":"10,11","required":false,"schema":{"type":"string","description":"Exclude brand IDs (CSV).","example":"10,11"}},{"in":"query","name":"creative_categories","description":"Creative category IDs (CSV). Valid IDs: 1=Before and After, 2=Testimonial - Reviews, 8=Promotion and Discount, 11=FAQ Explainers, 12=Holiday - Seasonal Theme, 14=Humor/Fun, 16=Reasons why, 17=Facts and Stats, 18=Features and Benefits, 19=Media and Press, 20=Us vs Them.","example":"1,17","required":false,"schema":{"type":"string","description":"Creative category IDs (CSV). Valid IDs: 1=Before and After, 2=Testimonial - Reviews, 8=Promotion and Discount, 11=FAQ Explainers, 12=Holiday - Seasonal Theme, 14=Humor/Fun, 16=Reasons why, 17=Facts and Stats, 18=Features and Benefits, 19=Media and Press, 20=Us vs Them.","example":"1,17"}},{"in":"query","name":"cta_types","description":"CTA types (CSV). Common values: SHOP_NOW, LEARN_MORE, SIGN_UP, DOWNLOAD, BOOK_NOW, CONTACT_US, GET_OFFER.","example":"SHOP_NOW,LEARN_MORE","required":false,"schema":{"type":"string","description":"CTA types (CSV). Common values: SHOP_NOW, LEARN_MORE, SIGN_UP, DOWNLOAD, BOOK_NOW, CONTACT_US, GET_OFFER.","example":"SHOP_NOW,LEARN_MORE"}},{"in":"query","name":"active_ads_count","description":"Minimum active ads per brand.","example":5,"required":false,"schema":{"type":"integer","description":"Minimum active ads per brand.","example":5}},{"in":"query","name":"ads_per_brand_limit","description":"Limit results per brand (1-50).","example":2,"required":false,"schema":{"type":"integer","description":"Limit results per brand (1-50).","example":2}},{"in":"query","name":"min_ad_copy_length","description":"Minimum ad copy (body) character length (1-10000).","example":100,"required":false,"schema":{"type":"integer","description":"Minimum ad copy (body) character length (1-10000).","example":100}},{"in":"query","name":"max_ad_copy_length","description":"Maximum ad copy (body) character length (1-10000). When both are present, must be >= min_ad_copy_length.","example":500,"required":false,"schema":{"type":"integer","description":"Maximum ad copy (body) character length (1-10000). When both are present, must be >= min_ad_copy_length.","example":500}},{"in":"query","name":"technologies","description":"Technologies the brand uses (CSV slugs, e.g. shopify,woocommerce,klaviyo).","example":"shopify,klaviyo","required":false,"schema":{"type":"string","description":"Technologies the brand uses (CSV slugs, e.g. shopify,woocommerce,klaviyo).","example":"shopify,klaviyo"}},{"in":"query","name":"brand_id","description":"Filter to a single brand by ID. Required when landing_page_url is supplied.","example":12345,"required":false,"schema":{"type":"integer","description":"Filter to a single brand by ID. Required when landing_page_url is supplied.","example":12345}},{"in":"query","name":"landing_page_url","description":"Filter by landing-page URL (max 2048 chars). Requires brand_id; pair with landing_page_match to control matching.","example":"https://example.com/offer","required":false,"schema":{"type":"string","description":"Filter by landing-page URL (max 2048 chars). Requires brand_id; pair with landing_page_match to control matching.","example":"https://example.com/offer"}},{"in":"query","name":"landing_page_match","description":"Landing-page URL matching mode. Only 'exact' is supported. Requires landing_page_url.","example":"exact","required":false,"schema":{"type":"string","enum":["exact"],"description":"Landing-page URL matching mode. Only 'exact' is supported. Requires landing_page_url.","example":"exact"}},{"in":"query","name":"ranking_method","description":"Ranking method. One of: impressions, run_time, duplication, custom. When 'custom', custom_weights must sum to 100.","example":"impressions","required":false,"schema":{"type":"string","enum":["impressions","run_time","duplication","custom"],"description":"Ranking method. One of: impressions, run_time, duplication, custom. When 'custom', custom_weights must sum to 100.","example":"impressions"}},{"in":"query","name":"percentile_floor","description":"Top-N-percent cutoff (0-99). The value is the percent kept: 1 = Top 1% (most selective), 99 = top 99% (least selective), 0 = all (no filter). Lower values narrow the result set; higher values broaden it. Computed per brand against the brand's ads eligible for ranking; ads with no rank for the selected method are not returned by a non-zero floor. Requires ranking_method; ignored when ranking_method=custom.","example":10,"required":false,"schema":{"type":"integer","description":"Top-N-percent cutoff (0-99). The value is the percent kept: 1 = Top 1% (most selective), 99 = top 99% (least selective), 0 = all (no filter). Lower values narrow the result set; higher values broaden it. Computed per brand against the brand's ads eligible for ranking; ads with no rank for the selected method are not returned by a non-zero floor. Requires ranking_method; ignored when ranking_method=custom.","example":10}},{"in":"query","name":"custom_weights","description":"Custom ranking weights (bracket notation, e.g. custom_weights[impressions]=60&custom_weights[run_time]=40). Each 0-100; required to sum to exactly 100 when ranking_method=custom.","required":false,"style":"deepObject","explode":true,"schema":{"type":"object","description":"Custom ranking weights. Each 0-100; must sum to 100 when ranking_method=custom.","properties":{"impressions":{"type":"integer","minimum":0,"maximum":100,"example":60},"run_time":{"type":"integer","minimum":0,"maximum":100,"example":40},"duplication":{"type":"integer","minimum":0,"maximum":100,"example":0}}}},{"in":"query","name":"collapse_variants","description":"Opt-in creative-variant collapsing. When true, duplicate variants collapse into one row stamped with variant_count. Accepts 1/0/true/false/on/off/yes/no.","example":"true","required":false,"schema":{"type":"string","enum":["1","0","true","false","on","off","yes","no"],"description":"Opt-in creative-variant collapsing. When true, duplicate variants collapse into one row stamped with variant_count. Accepts 1/0/true/false/on/off/yes/no.","example":"true"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":false,"data":[{"id":123,"external_id":"9999999","platform":"facebook","display_format":"video","title":"Great offer","body":"Check out our latest deal...","landing_page":"https://example.com","link_description":"Shop now","cta_type":"SHOP_NOW","cta_text":"Shop Now","start_date":"2025-01-05","end_date":null,"days_active":21,"active_in_library":1,"used_count":4,"is_aaa_eligible":1,"age_audience_min":25,"age_audience_max":34,"gender_audience":"All","eu_total_reach":5000,"ad_spend_range_score":3,"ad_spend_range_score_title":"$2,001 - $5,000","performance_score":120,"performance_score_title":"Winning","share_url":"https://app.gethookd.ai/share/ad/123","brand":{"external_id":"2016485295279615","name":"Acme","logo_url":"https://example.com/logo.png","active_ads":109},"media":[{"type":"video","url":"https://example.com/video.mp4","resized_url":"https://example.com/video-resized.mp4","thumbnail_url":"https://example.com/thumb.jpg","video_length":30}],"ad_cards":[{"title":"Variant A","body":"Ad card body text","caption":"Caption text","cta_text":"Shop Now","cta_type":"SHOP_NOW","landing_page":"https://example.com/variant-a","media":[{"type":"image","url":"https://example.com/card.jpg","resized_url":"https://example.com/card-resized.jpg","thumbnail_url":"https://example.com/card-thumb.jpg","video_length":0}]}]}],"used_credits":0.2,"remaining_credits":1199.8,"sorting":{"column":"created_at","direction":"desc"},"filters":{"platforms":["facebook"]}},"properties":{"errors":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicAd"}},"used_credits":{"type":"number","nullable":true,"example":0.2},"remaining_credits":{"type":"number","example":1199.8},"sorting":{"type":"object","properties":{"column":{"type":"string","example":"created_at"},"direction":{"type":"string","example":"desc"}}},"filters":{"type":"object","properties":{"platforms":{"type":"array","items":{"type":"string"},"example":["facebook"]}}}}}}}},"401":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Unauthenticated."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Unauthenticated."}}}}}},"402":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Not enough credits"},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Not enough credits"}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Unrecognized parameter(s): foo"},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Unrecognized parameter(s): foo"}}}}}}},"tags":["Public API v1 — Explore"]}},"/api/v1/brandspy":{"get":{"summary":"List spied brands with pagination.\n\nRequires a Public API token with scope: brand-spy:read.","operationId":"listSpiedBrandsWithPagination","description":"","parameters":[{"in":"query","name":"page","description":"The page number.","example":1,"required":false,"schema":{"type":"integer","description":"The page number.","example":1}},{"in":"query","name":"per_page","description":"Number of items per page (1-100). Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Number of items per page (1-100). Defaults to 20.","example":20}},{"in":"query","name":"sort_column","description":"Sort column. One of: created_at, active_ads, inactive_ads, last_spied_at, ads_change, growth_percent.","example":"created_at","required":false,"schema":{"type":"string","enum":["created_at","active_ads","inactive_ads","last_spied_at","ads_change","growth_percent"],"description":"Sort column. One of: created_at, active_ads, inactive_ads, last_spied_at, ads_change (absolute ads delta), growth_percent (percentage ads delta).","example":"created_at"}},{"in":"query","name":"sort_direction","description":"Sort direction. One of: asc, desc.","example":"desc","required":false,"schema":{"type":"string","description":"Sort direction. One of: asc, desc.","example":"desc"}},{"in":"query","name":"search","description":"Free-text substring match against the spied brand name.","example":"nike","required":false,"schema":{"type":"string","description":"Free-text substring match against the spied brand name.","example":"nike"}},{"in":"query","name":"parent_categories","description":"Filter by niche / parent-category IDs (CSV of integers).","example":"5,11","required":false,"schema":{"type":"string","description":"Filter by niche / parent-category IDs (CSV of integers).","example":"5,11"}},{"in":"query","name":"favourites_only","description":"When true, return only brands marked as favourite.","example":true,"required":false,"schema":{"type":"boolean","description":"When true, return only brands marked as favourite.","example":true}},{"in":"query","name":"folders","description":"Filter by folder / collection IDs owned by your workspace (CSV of integers).","example":"12,34","required":false,"schema":{"type":"string","description":"Filter by folder / collection IDs owned by your workspace (CSV of integers).","example":"12,34"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":false,"data":[{"id":1,"brand_id":456,"brand_external_id":"2016485295279615","brand_name":"Acme","brand_logo_url":"https://example.com/logo.png","status":"1","active_ads":109,"inactive_ads":23,"videos":45,"images":64,"carousels":23,"created_at":"2025-01-15T10:30:00+00:00","last_spied_at":"2025-03-01T14:00:00+00:00"}],"meta":{"current_page":1,"per_page":20,"total":5,"last_page":1},"used_credits":0.2,"remaining_credits":1199.8},"properties":{"errors":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicSpiedBrand"}},"meta":{"$ref":"#/components/schemas/PaginationMeta"},"used_credits":{"type":"number","nullable":true,"example":0.2},"remaining_credits":{"type":"number","example":1199.8}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"}},"tags":["Public API v1 — BrandSpy"]},"post":{"summary":"Add a brand to BrandSpy.\n\nRequires a Public API token with scope: brand-spy:write.","operationId":"addBrandToBrandSpy","description":"","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["brand_id"],"properties":{"brand_id":{"type":"integer","description":"The ID of the brand to spy on. Must exist in the brands table.","example":456}}}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":false,"data":{"id":1,"brand_id":456,"brand_external_id":"2016485295279615","brand_name":"Acme","brand_logo_url":"https://example.com/logo.png","status":"1","active_ads":109,"inactive_ads":0,"videos":0,"images":0,"carousels":0,"created_at":"2025-03-01T14:00:00+00:00","last_spied_at":null},"message":"Brand added to BrandSpy.","used_credits":0.2,"remaining_credits":1199.8},"properties":{"errors":{"type":"boolean","example":false},"data":{"$ref":"#/components/schemas/PublicSpiedBrand"},"message":{"type":"string","example":"Brand added to BrandSpy."},"used_credits":{"type":"number","nullable":true,"example":0.2},"remaining_credits":{"type":"number","example":1199.8}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"409":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Brand is already being spied on."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Brand is already being spied on."}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"The brand id field is required."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"The brand id field is required."}}}}}}},"tags":["Public API v1 — BrandSpy"]}},"/api/v1/brandspy/{brand_id}":{"get":{"summary":"Show brand details with paginated ads.\n\nRequires a Public API token with scope: brand-spy:read.","operationId":"showSpiedBrandWithAds","description":"","parameters":[{"in":"path","name":"brand_id","description":"The brand ID.","example":456,"required":true,"schema":{"type":"integer","description":"The brand ID.","example":456}},{"in":"query","name":"page","description":"The page number.","example":1,"required":false,"schema":{"type":"integer","description":"The page number.","example":1}},{"in":"query","name":"per_page","description":"Number of ads per page (1-100). Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Number of ads per page (1-100). Defaults to 20.","example":20}},{"in":"query","name":"status","description":"Filter by ad status. One of: active, inactive.","example":"active","required":false,"schema":{"type":"string","description":"Filter by ad status. One of: active, inactive.","example":"active"}},{"in":"query","name":"platform","description":"Filter by platform. One of: facebook, instagram, tiktok, youtube, twitter, pinterest, snapchat, linkedin.","example":"facebook","required":false,"schema":{"type":"string","description":"Filter by platform. One of: facebook, instagram, tiktok, youtube, twitter, pinterest, snapchat, linkedin.","example":"facebook"}},{"in":"query","name":"ad-format","description":"Filter by ad format. One of: image, video, carousel, dco, dpa, event, page_like, multi_images.","example":"video","required":false,"schema":{"type":"string","description":"Filter by ad format. One of: image, video, carousel, dco, dpa, event, page_like, multi_images.","example":"video"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":false,"data":{"id":1,"brand_id":456,"brand_external_id":"2016485295279615","brand_name":"Acme","brand_logo_url":"https://example.com/logo.png","status":"1","active_ads":109,"inactive_ads":23,"videos":45,"images":64,"carousels":23,"created_at":"2025-01-15T10:30:00+00:00","last_spied_at":"2025-03-01T14:00:00+00:00","ads":{"data":[],"current_page":1,"per_page":20,"total":109,"last_page":6}},"used_credits":0.2,"remaining_credits":1199.8},"properties":{"errors":{"type":"boolean","example":false},"data":{"allOf":[{"$ref":"#/components/schemas/PublicSpiedBrand"},{"type":"object","properties":{"ads":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicAd"}},"current_page":{"type":"integer","example":1},"per_page":{"type":"integer","example":20},"total":{"type":"integer","example":109},"last_page":{"type":"integer","example":6}}}}}]},"used_credits":{"type":"number","nullable":true,"example":0.2},"remaining_credits":{"type":"number","example":1199.8}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"code":"brand_not_spied","brand_id":456,"message":"Brand 456 exists but is not on this workspace's spy list.","self_correct_hint":"Add brand 456 to this workspace's spy list first: POST /api/v1/brandspy with {\"brand_id\": 456} (requires scope brand-spy:write), then retry."},"properties":{"errors":{"type":"boolean","example":true},"code":{"type":"string","enum":["brand_not_spied","not_found"],"example":"brand_not_spied","description":"`brand_not_spied` — the brand exists but is not on this workspace's spy list. `not_found` — no brand with that id exists. A brand spied only by a different workspace deliberately reports `brand_not_spied`, byte-identical to the unspied case."},"brand_id":{"type":"integer","nullable":true,"example":456,"description":"Echoed back on `brand_not_spied`; absent on `not_found`."},"message":{"type":"string","example":"Brand 456 exists but is not on this workspace's spy list.","description":"`not_found` returns \"Brand {id} not found.\" instead."},"self_correct_hint":{"type":"string","nullable":true,"example":"Add brand 456 to this workspace's spy list first: POST /api/v1/brandspy with {\"brand_id\": 456} (requires scope brand-spy:write), then retry.","description":"Present only on `brand_not_spied` for the read endpoints. Omitted on `DELETE /api/v1/brandspy/{brand_id}` and on `not_found`."}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Invalid status. Allowed: active, inactive."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Invalid status. Allowed: active, inactive."}}}}}}},"tags":["Public API v1 — BrandSpy"]},"delete":{"summary":"Remove a brand from BrandSpy.\n\nRequires a Public API token with scope: brand-spy:write.","operationId":"removeBrandFromBrandSpy","description":"","parameters":[{"in":"path","name":"brand_id","description":"The brand ID to remove.","example":456,"required":true,"schema":{"type":"integer","description":"The brand ID to remove.","example":456}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":false,"message":"Brand removed from BrandSpy.","used_credits":0.2,"remaining_credits":1199.8},"properties":{"errors":{"type":"boolean","example":false},"message":{"type":"string","example":"Brand removed from BrandSpy."},"used_credits":{"type":"number","nullable":true,"example":0.2},"remaining_credits":{"type":"number","example":1199.8}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"code":"brand_not_spied","brand_id":456,"message":"Brand 456 exists but is not on this workspace's spy list."},"properties":{"errors":{"type":"boolean","example":true},"code":{"type":"string","enum":["brand_not_spied","not_found"],"example":"brand_not_spied","description":"`brand_not_spied` — the brand exists but is not on this workspace's spy list. `not_found` — no brand with that id exists. A brand spied only by a different workspace deliberately reports `brand_not_spied`, byte-identical to the unspied case."},"brand_id":{"type":"integer","nullable":true,"example":456,"description":"Echoed back on `brand_not_spied`; absent on `not_found`."},"message":{"type":"string","example":"Brand 456 exists but is not on this workspace's spy list.","description":"`not_found` returns \"Brand {id} not found.\" instead. No `self_correct_hint` is returned on this write endpoint: the route also accepts a `spied_brands.id`, so an \"add brand {id} first\" hint could induce a write against an unrelated brand."}}}}}}},"tags":["Public API v1 — BrandSpy"]}},"/api/v1/swipefile":{"get":{"summary":"List saved ads in swipe file with pagination.\n\nRequires a Public API token with scope: swipe-file:read.","operationId":"listSavedAdsInSwipeFile","description":"","parameters":[{"in":"query","name":"page","description":"The page number.","example":1,"required":false,"schema":{"type":"integer","description":"The page number.","example":1}},{"in":"query","name":"per_page","description":"Number of items per page (1-100). Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Number of items per page (1-100). Defaults to 20.","example":20}},{"in":"query","name":"sort_column","description":"Sort column. One of: created_at, start_date, days_active, used_count, popularity.","example":"created_at","required":false,"schema":{"type":"string","enum":["created_at","start_date","days_active","used_count","popularity"],"description":"Sort column. One of: created_at, start_date, days_active, used_count, popularity.","example":"created_at"}},{"in":"query","name":"sort_direction","description":"Sort direction. One of: asc, desc.","example":"desc","required":false,"schema":{"type":"string","description":"Sort direction. One of: asc, desc.","example":"desc"}},{"in":"query","name":"search","description":"Free-text substring match against the saved ad (max 500 chars).","example":"skincare","required":false,"schema":{"type":"string","description":"Free-text substring match against the saved ad (max 500 chars).","example":"skincare"}},{"in":"query","name":"active_in_library","description":"Filter by Meta ad-library activity. 1 = still active, 0 = inactive.","example":1,"required":false,"schema":{"type":"integer","enum":[0,1],"description":"Filter by Meta ad-library activity. 1 = still active, 0 = inactive.","example":1}},{"in":"query","name":"days_active","description":"Minimum number of days the ad has been running.","example":30,"required":false,"schema":{"type":"integer","description":"Minimum number of days the ad has been running.","example":30}},{"in":"query","name":"used_count","description":"Minimum number of times the creative has been used.","example":3,"required":false,"schema":{"type":"integer","description":"Minimum number of times the creative has been used.","example":3}},{"in":"query","name":"ads_per_brand_limit","description":"Limit results per brand (1-50).","example":2,"required":false,"schema":{"type":"integer","description":"Limit results per brand (1-50).","example":2}},{"in":"query","name":"min_ad_copy_length","description":"Minimum ad copy (body) character length (1-10000).","example":100,"required":false,"schema":{"type":"integer","description":"Minimum ad copy (body) character length (1-10000).","example":100}},{"in":"query","name":"max_ad_copy_length","description":"Maximum ad copy (body) character length (1-10000). When both are present, must be >= min_ad_copy_length.","example":500,"required":false,"schema":{"type":"integer","description":"Maximum ad copy (body) character length (1-10000). When both are present, must be >= min_ad_copy_length.","example":500}},{"in":"query","name":"from_date","description":"Start of the ad start-date range (inclusive). Required when to_date is supplied.","example":"2025-01-01","required":false,"schema":{"type":"string","format":"date","description":"Start of the ad start-date range (inclusive). Required when to_date is supplied.","example":"2025-01-01"}},{"in":"query","name":"to_date","description":"End of the ad start-date range (inclusive). Must be on/after from_date.","example":"2025-03-31","required":false,"schema":{"type":"string","format":"date","description":"End of the ad start-date range (inclusive). Must be on/after from_date.","example":"2025-03-31"}},{"in":"query","name":"parent_categories[]","description":"Filter by niche / parent-category IDs. Repeat the param (parent_categories[]=5&parent_categories[]=11). Max 50.","required":false,"style":"form","explode":true,"schema":{"type":"array","items":{"type":"integer","minimum":1},"example":[5,11]}},{"in":"query","name":"excluded_brands[]","description":"Brand IDs to exclude. Repeat the param (excluded_brands[]=1&excluded_brands[]=2). Max 100.","required":false,"style":"form","explode":true,"schema":{"type":"array","items":{"type":"integer","minimum":1},"example":[1,2]}},{"in":"query","name":"creative_categories[]","description":"Creative-category IDs. Repeat the param (creative_categories[]=3). Max 50.","required":false,"style":"form","explode":true,"schema":{"type":"array","items":{"type":"integer","minimum":1},"example":[3]}},{"in":"query","name":"tags[]","description":"Tag IDs. Repeat the param (tags[]=7). Max 50.","required":false,"style":"form","explode":true,"schema":{"type":"array","items":{"type":"integer","minimum":1},"example":[7]}},{"in":"query","name":"languages[]","description":"Language codes. Repeat the param (languages[]=en). Max 50.","required":false,"style":"form","explode":true,"schema":{"type":"array","items":{"type":"string","maxLength":10},"example":["en","es"]}},{"in":"query","name":"platforms[]","description":"Platform slugs. Repeat the param (platforms[]=facebook). Max 20.","required":false,"style":"form","explode":true,"schema":{"type":"array","items":{"type":"string","maxLength":50},"example":["facebook","instagram"]}},{"in":"query","name":"winning_scores[]","description":"Performance tiers. Repeat the param (winning_scores[]=winning). Each one of: testing, scaling, winning. Max 20.","required":false,"style":"form","explode":true,"schema":{"type":"array","items":{"type":"string","enum":["testing","scaling","winning"]},"example":["winning"]}},{"in":"query","name":"video_lengths[]","description":"Video-length buckets. Repeat the param (video_lengths[]=short). Max 20.","required":false,"style":"form","explode":true,"schema":{"type":"array","items":{"type":"string","maxLength":50},"example":["short"]}},{"in":"query","name":"display_formats[]","description":"Display formats. Repeat the param (display_formats[]=video). Max 20.","required":false,"style":"form","explode":true,"schema":{"type":"array","items":{"type":"string","maxLength":50},"example":["video","image"]}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":false,"data":[],"used_credits":0.2,"remaining_credits":1199.8,"meta":{"current_page":1,"per_page":20,"total":50,"last_page":3}},"properties":{"errors":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicAd"}},"used_credits":{"type":"number","nullable":true,"example":0.2},"remaining_credits":{"type":"number","example":1199.8},"meta":{"$ref":"#/components/schemas/PaginationMeta"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"The sort column field is invalid."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"The sort column field is invalid."}}}}}}},"tags":["Public API v1 — Swipe File"]},"post":{"summary":"Save an ad to swipe file.\n\nRequires a Public API token with scope: swipe-file:write.","operationId":"saveAdToSwipeFile","description":"","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ad_id"],"properties":{"ad_id":{"type":"integer","description":"The ID of the ad to save. Must exist in the ads table.","example":123}}}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":false,"data":{"ad_id":123},"message":"Ad saved to swipe file.","used_credits":0.2,"remaining_credits":1199.8},"properties":{"errors":{"type":"boolean","example":false},"data":{"type":"object","properties":{"ad_id":{"type":"integer","example":123}}},"message":{"type":"string","example":"Ad saved to swipe file."},"used_credits":{"type":"number","nullable":true,"example":0.2},"remaining_credits":{"type":"number","example":1199.8}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"409":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Ad is already in your swipe file."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Ad is already in your swipe file."}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"The ad id field is required."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"The ad id field is required."}}}}}}},"tags":["Public API v1 — Swipe File"]}},"/api/v1/swipefile/{ad_id}":{"delete":{"summary":"Remove an ad from swipe file.\n\nRequires a Public API token with scope: swipe-file:write.","operationId":"removeAdFromSwipeFile","description":"","parameters":[{"in":"path","name":"ad_id","description":"The ad ID to remove.","example":123,"required":true,"schema":{"type":"integer","description":"The ad ID to remove.","example":123}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":false,"message":"Ad removed from swipe file.","used_credits":0.2,"remaining_credits":1199.8},"properties":{"errors":{"type":"boolean","example":false},"message":{"type":"string","example":"Ad removed from swipe file."},"used_credits":{"type":"number","nullable":true,"example":0.2},"remaining_credits":{"type":"number","example":1199.8}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Ad not found in your swipe file."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Ad not found in your swipe file."}}}}}}},"tags":["Public API v1 — Swipe File"]}},"/api/v1/boards":{"get":{"summary":"List boards with pagination.\n\nRequires a Public API token with scope: boards:read.","operationId":"listBoards","description":"","parameters":[{"in":"query","name":"page","description":"The page number.","example":1,"required":false,"schema":{"type":"integer","description":"The page number.","example":1}},{"in":"query","name":"per_page","description":"Number of items per page (1-100). Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Number of items per page (1-100). Defaults to 20.","example":20}},{"in":"query","name":"sort_column","description":"Sort column. One of: created_at, updated_at, name.","example":"created_at","required":false,"schema":{"type":"string","description":"Sort column. One of: created_at, updated_at, name.","example":"created_at"}},{"in":"query","name":"sort_direction","description":"Sort direction. One of: asc, desc.","example":"desc","required":false,"schema":{"type":"string","description":"Sort direction. One of: asc, desc.","example":"desc"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":false,"data":[{"id":1,"name":"Top Performers","slug":"top-performers-1","ad_count":25,"is_public":true,"public_url":"https://app.gethookd.ai/share/board/top-performers-1","created_at":"2025-01-15T10:30:00+00:00","updated_at":"2025-03-01T14:00:00+00:00"}],"meta":{"current_page":1,"per_page":20,"total":3,"last_page":1},"used_credits":0.2,"remaining_credits":1199.8},"properties":{"errors":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicBoard"}},"meta":{"$ref":"#/components/schemas/PaginationMeta"},"used_credits":{"type":"number","nullable":true,"example":0.2},"remaining_credits":{"type":"number","example":1199.8}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"}},"tags":["Public API v1 — Boards"]},"post":{"summary":"Create a new board.\n\nRequires a Public API token with scope: boards:write.","operationId":"createBoard","description":"","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"The board name. Max 255 characters.","example":"Top Performers","maxLength":255},"is_public":{"type":"boolean","description":"Whether the board is publicly shareable. Defaults to false. Admin-only: sending `true` from a token whose creator is not an admin returns 403 (`User not allowed to change global status.`) and the board is not created. Sending `false`, or omitting the field, is allowed for every caller.","example":false}}}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":false,"data":{"id":1,"name":"Top Performers","slug":"top-performers-1","ad_count":0,"is_public":false,"public_url":null,"created_at":"2025-03-01T14:00:00+00:00","updated_at":"2025-03-01T14:00:00+00:00"},"message":"Board created.","used_credits":0.2,"remaining_credits":1199.8},"properties":{"errors":{"type":"boolean","example":false},"data":{"$ref":"#/components/schemas/PublicBoard"},"message":{"type":"string","example":"Board created."},"used_credits":{"type":"number","nullable":true,"example":0.2},"remaining_credits":{"type":"number","example":1199.8}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"403":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"User not allowed to change global status."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"User not allowed to change global status."}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"The name field is required."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"The name field is required."}}}}}}},"tags":["Public API v1 — Boards"]}},"/api/v1/boards/{board_id}":{"get":{"summary":"Get board details with paginated ads.\n\nRequires a Public API token with scope: boards:read.","operationId":"getBoardWithAds","description":"","parameters":[{"in":"path","name":"board_id","description":"The board ID.","example":1,"required":true,"schema":{"type":"integer","description":"The board ID.","example":1}},{"in":"query","name":"page","description":"The page number for ads.","example":1,"required":false,"schema":{"type":"integer","description":"The page number for ads.","example":1}},{"in":"query","name":"per_page","description":"Number of ads per page (1-100). Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Number of ads per page (1-100). Defaults to 20.","example":20}},{"in":"query","name":"sort_column","description":"Sort the board's ads. One of: created_at, start_date, days_active, used_count. Defaults to created_at.","example":"created_at","required":false,"schema":{"type":"string","enum":["created_at","start_date","days_active","used_count"],"description":"Sort the board's ads. One of: created_at, start_date, days_active, used_count. Defaults to created_at.","example":"created_at"}},{"in":"query","name":"sort_direction","description":"Sort direction for the board's ads. One of: asc, desc. Defaults to desc.","example":"desc","required":false,"schema":{"type":"string","enum":["asc","desc"],"description":"Sort direction for the board's ads. One of: asc, desc. Defaults to desc.","example":"desc"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":false,"data":{"id":1,"name":"Top Performers","slug":"top-performers-1","ad_count":25,"is_public":true,"public_url":"https://app.gethookd.ai/share/board/top-performers-1","created_at":"2025-01-15T10:30:00+00:00","updated_at":"2025-03-01T14:00:00+00:00","ads":{"data":[],"current_page":1,"per_page":20,"total":25,"last_page":2}},"used_credits":0.2,"remaining_credits":1199.8},"properties":{"errors":{"type":"boolean","example":false},"data":{"allOf":[{"$ref":"#/components/schemas/PublicBoard"},{"type":"object","properties":{"ads":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicAd"}},"current_page":{"type":"integer","example":1},"per_page":{"type":"integer","example":20},"total":{"type":"integer","example":25},"last_page":{"type":"integer","example":2}}}}}]},"used_credits":{"type":"number","nullable":true,"example":0.2},"remaining_credits":{"type":"number","example":1199.8}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Board not found."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Board not found."}}}}}}},"tags":["Public API v1 — Boards"]},"put":{"summary":"Update a board.\n\nRequires a Public API token with scope: boards:write.","operationId":"updateBoard","description":"","parameters":[{"in":"path","name":"board_id","description":"The board ID.","example":1,"required":true,"schema":{"type":"integer","description":"The board ID.","example":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The board name. Max 255 characters.","example":"Updated Board Name","maxLength":255},"is_public":{"type":"boolean","description":"Whether the board is publicly shareable. Admin-only: changing this value from a token whose creator is not an admin returns 403 (`User not allowed to change global status.`) and the board is left untouched — in both directions. Omitting the field, or re-sending the value the board already has, is allowed for every caller.","example":true}}}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":false,"data":{"id":1,"name":"Updated Board Name","slug":"updated-board-name-1","ad_count":25,"is_public":true,"public_url":"https://app.gethookd.ai/share/board/updated-board-name-1","created_at":"2025-01-15T10:30:00+00:00","updated_at":"2025-03-05T14:00:00+00:00"},"used_credits":0.2,"remaining_credits":1199.8},"properties":{"errors":{"type":"boolean","example":false},"data":{"$ref":"#/components/schemas/PublicBoard"},"used_credits":{"type":"number","nullable":true,"example":0.2},"remaining_credits":{"type":"number","example":1199.8}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"403":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"User not allowed to change global status."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"User not allowed to change global status."}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Board not found or you do not have permission to update it."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Board not found or you do not have permission to update it."}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"The name must be a string."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"The name must be a string."}}}}}}},"tags":["Public API v1 — Boards"]},"delete":{"summary":"Delete a board.\n\nRequires a Public API token with scope: boards:write.","operationId":"deleteBoard","description":"","parameters":[{"in":"path","name":"board_id","description":"The board ID to delete.","example":1,"required":true,"schema":{"type":"integer","description":"The board ID to delete.","example":1}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":false,"message":"Board deleted.","used_credits":0.2,"remaining_credits":1199.8},"properties":{"errors":{"type":"boolean","example":false},"message":{"type":"string","example":"Board deleted."},"used_credits":{"type":"number","nullable":true,"example":0.2},"remaining_credits":{"type":"number","example":1199.8}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Board not found or you do not have permission to delete it."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Board not found or you do not have permission to delete it."}}}}}}},"tags":["Public API v1 — Boards"]}},"/api/v1/boards/{board_id}/ads":{"post":{"summary":"Add an ad to a board.\n\nRequires a Public API token with scope: boards:write.","operationId":"addAdToBoard","description":"","parameters":[{"in":"path","name":"board_id","description":"The board ID.","example":1,"required":true,"schema":{"type":"integer","description":"The board ID.","example":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ad_id"],"properties":{"ad_id":{"type":"integer","description":"The ID of the ad to add. Must exist in the ads table.","example":123}}}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":false,"data":{"board_id":1,"ad_id":123},"message":"Ad added to board.","used_credits":0.2,"remaining_credits":1199.8},"properties":{"errors":{"type":"boolean","example":false},"data":{"type":"object","properties":{"board_id":{"type":"integer","example":1},"ad_id":{"type":"integer","example":123}}},"message":{"type":"string","example":"Ad added to board."},"used_credits":{"type":"number","nullable":true,"example":0.2},"remaining_credits":{"type":"number","example":1199.8}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Board not found."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Board not found."}}}}}},"409":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Ad is already on this board."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Ad is already on this board."}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"The ad id field is required."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"The ad id field is required."}}}}}}},"tags":["Public API v1 — Boards"]}},"/api/v1/boards/{board_id}/ads/{ad_id}":{"delete":{"summary":"Remove an ad from a board.\n\nRequires a Public API token with scope: boards:write.","operationId":"removeAdFromBoard","description":"","parameters":[{"in":"path","name":"board_id","description":"The board ID.","example":1,"required":true,"schema":{"type":"integer","description":"The board ID.","example":1}},{"in":"path","name":"ad_id","description":"The ad ID to remove.","example":123,"required":true,"schema":{"type":"integer","description":"The ad ID to remove.","example":123}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":false,"message":"Ad removed from board.","used_credits":0.2,"remaining_credits":1199.8},"properties":{"errors":{"type":"boolean","example":false},"message":{"type":"string","example":"Ad removed from board."},"used_credits":{"type":"number","nullable":true,"example":0.2},"remaining_credits":{"type":"number","example":1199.8}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Ad not found on this board."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Ad not found on this board."}}}}}}},"tags":["Public API v1 — Boards"]}},"/api/v1/clone-ads":{"get":{"summary":"List clone ads with pagination.\n\nRequires a Public API token with scope: clone-ads:read.","operationId":"listCloneAds","description":"","parameters":[{"in":"query","name":"page","description":"The page number.","example":1,"required":false,"schema":{"type":"integer","description":"The page number.","example":1}},{"in":"query","name":"per_page","description":"Number of items per page (1-100). Defaults to 20.","example":20,"required":false,"schema":{"type":"integer","description":"Number of items per page (1-100). Defaults to 20.","example":20}},{"in":"query","name":"sort_direction","description":"Sort direction. One of: asc, desc. Sorts by created_at.","example":"desc","required":false,"schema":{"type":"string","description":"Sort direction. One of: asc, desc. Sorts by created_at.","example":"desc"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":false,"data":[{"id":1,"title":"Clone of Ad #123","created_at":"2025-03-01T14:00:00+00:00","updated_at":"2025-03-01T14:05:00+00:00","prompts":[{"id":1,"prompt":"Clone ad #123","aspect_ratio":"Square","in_progress":false,"created_at":"2025-03-01T14:00:00+00:00","media":[{"id":1,"url":"https://example.com/generated-image.png","aspect_ratio":"Square","is_image_reference":false,"order":1}]}]}],"meta":{"current_page":1,"per_page":20,"total":10,"last_page":1},"used_credits":0.2,"remaining_credits":1199.8},"properties":{"errors":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicCloneAd"}},"meta":{"$ref":"#/components/schemas/PaginationMeta"},"used_credits":{"type":"number","nullable":true,"example":0.2},"remaining_credits":{"type":"number","example":1199.8}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"}},"tags":["Public API v1 — Clone Ads"]},"post":{"summary":"Create a clone ad from an existing ad.\n\nRequires a Public API token with scope: clone-ads:write. Generates AI image variations based on the source ad.","operationId":"createCloneAd","description":"","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ad_id"],"properties":{"ad_id":{"type":"integer","description":"The ID of the source ad to clone. Must exist in the ads table.","example":123},"product_id":{"type":"integer","description":"Optional product ID to drive the rich clone-ad pipeline. When provided it must belong to your workspace; otherwise a product is auto-resolved from the source ad.","example":456,"nullable":true},"prompt":{"type":"string","description":"Custom prompt for image generation. Max 4000 characters.","example":"Create a vibrant version with summer colors","maxLength":4000,"nullable":true},"aspect_ratio":{"type":"string","description":"Aspect ratio for generated images. One of: 'Square (1:1)', 'Portrait (9:16)', 'Landscape (16:9)', 'Portrait (2:3)', 'Landscape (3:2)', 'Portrait (3:4)', 'Landscape (4:3)'. Legacy bare names (Square, Portrait, Landscape) are also accepted and normalized. Defaults to 'Square (1:1)'.","example":"Square (1:1)","enum":["Square (1:1)","Portrait (9:16)","Landscape (16:9)","Portrait (2:3)","Landscape (3:2)","Portrait (3:4)","Landscape (4:3)"],"nullable":true},"variations_count":{"type":"integer","description":"Number of image variations to generate (1-10). Defaults to 3.","example":3,"minimum":1,"maximum":10,"nullable":true}}}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":false,"data":{"id":1,"title":"Clone of Ad #123","created_at":"2025-03-01T14:00:00+00:00","updated_at":"2025-03-01T14:00:00+00:00","prompts":[{"id":1,"prompt":"Create a vibrant version with summer colors","aspect_ratio":"Square","in_progress":true,"created_at":"2025-03-01T14:00:00+00:00","media":[]}]},"used_credits":0.6,"remaining_credits":1199.4,"message":"Clone ad generated successfully."},"properties":{"errors":{"type":"boolean","example":false},"data":{"$ref":"#/components/schemas/PublicCloneAd"},"used_credits":{"type":"number","nullable":true,"example":0.6},"remaining_credits":{"type":"number","example":1199.4},"message":{"type":"string","example":"Clone ad generated successfully."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"The ad id field is required."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"The ad id field is required."}}}}}}},"tags":["Public API v1 — Clone Ads"]}},"/api/v1/clone-ads/{clone_id}":{"get":{"summary":"Get a clone ad by ID.\n\nRequires a Public API token with scope: clone-ads:read.","operationId":"getCloneAd","description":"","parameters":[{"in":"path","name":"clone_id","description":"The clone ad ID.","example":1,"required":true,"schema":{"type":"integer","description":"The clone ad ID.","example":1}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":false,"data":{"id":1,"title":"Clone of Ad #123","created_at":"2025-03-01T14:00:00+00:00","updated_at":"2025-03-01T14:05:00+00:00","prompts":[{"id":1,"prompt":"Clone ad #123","aspect_ratio":"Square","in_progress":false,"created_at":"2025-03-01T14:00:00+00:00","media":[{"id":1,"url":"https://example.com/generated-image.png","aspect_ratio":"Square","is_image_reference":false,"order":1}]}]},"used_credits":0.2,"remaining_credits":1199.8},"properties":{"errors":{"type":"boolean","example":false},"data":{"$ref":"#/components/schemas/PublicCloneAd"},"used_credits":{"type":"number","nullable":true,"example":0.2},"remaining_credits":{"type":"number","example":1199.8}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Clone ad not found."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Clone ad not found."}}}}}}},"tags":["Public API v1 — Clone Ads"]},"delete":{"summary":"Delete a clone ad.\n\nRequires a Public API token with scope: clone-ads:write.","operationId":"deleteCloneAd","description":"Soft-deletes the clone ad (and its prompts) for the current workspace. Billed as a flat write operation — used_credits reflects the configured write-tool cost (e.g. 0.01). Mirrors the MCP delete_clone_ad tool.","parameters":[{"in":"path","name":"clone_id","description":"The clone ad ID.","example":1,"required":true,"schema":{"type":"integer","description":"The clone ad ID.","example":1}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":false,"data":{"id":1,"active":false},"used_credits":0.01,"remaining_credits":1199.79,"message":"Clone ad deleted successfully."},"properties":{"errors":{"type":"boolean","example":false},"data":{"type":"object","properties":{"id":{"type":"integer","example":1},"active":{"type":"boolean","example":false}}},"used_credits":{"type":"number","nullable":true,"example":0.01},"remaining_credits":{"type":"number","nullable":true,"example":1199.79},"message":{"type":"string","example":"Clone ad deleted successfully."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"code":"not_found","message":"Clone ad not found."},"properties":{"errors":{"type":"boolean","example":true},"code":{"type":"string","example":"not_found"},"message":{"type":"string","example":"Clone ad not found."}}}}}}},"tags":["Public API v1 — Clone Ads"]}},"/api/v1/ads/{ad_id}":{"get":{"summary":"Get a single ad by ID.\n\nRequires a Public API token with scope: explore:read. Returns the same ad shape as the list endpoint. Single-ad lookup does not deduct credits.","operationId":"getAdById","description":"","parameters":[{"in":"path","name":"ad_id","description":"The internal ad ID.","example":12345,"required":true,"schema":{"type":"integer","description":"The internal ad ID.","example":12345}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"data":{"$ref":"#/components/schemas/PublicAd"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Ad not found."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Ad not found."}}}}}}},"tags":["Public API v1 — Explore"]}},"/api/v1/brands":{"get":{"summary":"Search the global brands catalog.\n\nRequires a Public API token with scope: brand-spy:read. No credit deduction — brand metadata is not billable.","operationId":"listBrands","description":"","parameters":[{"in":"query","name":"search","description":"Free-text substring match against the brand name.","example":"nike","required":false,"schema":{"type":"string","example":"nike"}},{"in":"query","name":"page","description":"The page number.","example":1,"required":false,"schema":{"type":"integer","example":1}},{"in":"query","name":"per_page","description":"Number of items per page (1-100). Defaults to 20.","example":50,"required":false,"schema":{"type":"integer","example":50}},{"in":"query","name":"sort_column","description":"Sort column. One of: name, active_ads, created_at. Defaults to name. Unrecognized values fall back to name (asc).","example":"active_ads","required":false,"schema":{"type":"string","example":"active_ads"}},{"in":"query","name":"sort_direction","description":"Sort direction. One of: asc, desc.","example":"desc","required":false,"schema":{"type":"string","example":"desc"}},{"in":"query","name":"parent_categories","description":"Niche / parent-category IDs (CSV). Non-integer values are silently dropped.","example":"5,16","required":false,"schema":{"type":"string","example":"5,16"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicBrand"}},"meta":{"$ref":"#/components/schemas/PaginationMeta"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"}},"tags":["Public API v1 — Brands"]}},"/api/v1/brands/{brand_id}":{"get":{"summary":"Look up a single brand by its internal ID.\n\nRequires a Public API token with scope: brand-spy:read. No credit deduction.","operationId":"getBrandById","description":"","parameters":[{"in":"path","name":"brand_id","description":"The internal brand ID.","example":12345,"required":true,"schema":{"type":"integer","description":"The internal brand ID.","example":12345}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"data":{"$ref":"#/components/schemas/PublicBrand"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Brand not found."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Brand not found."}}}}}}},"tags":["Public API v1 — Brands"]}},"/api/v1/brandspy/{brand_id}/top-ads":{"get":{"summary":"List a brand's currently-running top-performing ads.\n\nRequires a Public API token with scope: brand-spy:read. Ranked by performance score (with global impression rank as tiebreaker). The brand must already be spied on by the authenticated workspace. No credit deduction.","operationId":"listBrandSpyTopAds","description":"","parameters":[{"in":"path","name":"brand_id","description":"The internal brand ID, or the spied-brand record ID.","example":12345,"required":true,"schema":{"type":"integer","description":"The internal brand ID, or the spied-brand record ID.","example":12345}},{"in":"query","name":"limit","description":"Number of ads to return (1-50). Defaults to 10.","example":25,"required":false,"schema":{"type":"integer","example":25}},{"in":"query","name":"platform","description":"Filter by platform. One of: facebook, instagram, tiktok, youtube, twitter, pinterest, snapchat, linkedin.","example":"facebook","required":false,"schema":{"type":"string","example":"facebook"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicAd"}},"meta":{"type":"object","properties":{"result_count":{"type":"integer","example":25}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"code":"brand_not_spied","brand_id":456,"message":"Brand 456 exists but is not on this workspace's spy list.","self_correct_hint":"Add brand 456 to this workspace's spy list first: POST /api/v1/brandspy with {\"brand_id\": 456} (requires scope brand-spy:write), then retry."},"properties":{"errors":{"type":"boolean","example":true},"code":{"type":"string","enum":["brand_not_spied","not_found"],"example":"brand_not_spied","description":"`brand_not_spied` — the brand exists but is not on this workspace's spy list. `not_found` — no brand with that id exists. A brand spied only by a different workspace deliberately reports `brand_not_spied`, byte-identical to the unspied case."},"brand_id":{"type":"integer","nullable":true,"example":456,"description":"Echoed back on `brand_not_spied`; absent on `not_found`."},"message":{"type":"string","example":"Brand 456 exists but is not on this workspace's spy list.","description":"`not_found` returns \"Brand {id} not found.\" instead."},"self_correct_hint":{"type":"string","nullable":true,"example":"Add brand 456 to this workspace's spy list first: POST /api/v1/brandspy with {\"brand_id\": 456} (requires scope brand-spy:write), then retry.","description":"Present only on `brand_not_spied` for the read endpoints. Omitted on `DELETE /api/v1/brandspy/{brand_id}` and on `not_found`."}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Invalid platform. Allowed: facebook, instagram, tiktok, youtube, twitter, pinterest, snapchat, linkedin."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Invalid platform. Allowed: facebook, instagram, tiktok, youtube, twitter, pinterest, snapchat, linkedin."}}}}}}},"tags":["Public API v1 — BrandSpy"]}},"/api/v1/searches":{"get":{"summary":"List saved searches with pagination.\n\nRequires a Public API token with scope: searches:read.","operationId":"listSavedSearches","description":"","parameters":[{"in":"query","name":"page","description":"The page number.","example":1,"required":false,"schema":{"type":"integer","example":1}},{"in":"query","name":"per_page","description":"Number of items per page (1-100). Defaults to 10.","example":10,"required":false,"schema":{"type":"integer","example":10}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicSavedSearch"}},"used_credits":{"type":"number","nullable":true,"example":0.05},"remaining_credits":{"type":"number","example":1199.95},"meta":{"$ref":"#/components/schemas/PaginationMeta"}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"}},"tags":["Public API v1 — Saved Searches"]},"post":{"summary":"Create a saved search.\n\nRequires a Public API token with scope: searches:write.","operationId":"createSavedSearch","description":"","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicSavedSearchInput"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"message":{"type":"string","example":"Saved search created successfully."},"data":{"$ref":"#/components/schemas/PublicSavedSearch"},"used_credits":{"type":"number","nullable":true,"example":0.05},"remaining_credits":{"type":"number","example":1199.95}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"The name field is required."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"The name field is required."}}}}}},"500":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Unable to store saved search."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Unable to store saved search."}}}}}}},"tags":["Public API v1 — Saved Searches"]}},"/api/v1/searches/{search_id}":{"get":{"summary":"Get a saved search by ID.\n\nRequires a Public API token with scope: searches:read.","operationId":"getSavedSearch","description":"","parameters":[{"in":"path","name":"search_id","description":"The saved search ID.","example":42,"required":true,"schema":{"type":"integer","description":"The saved search ID.","example":42}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"data":{"$ref":"#/components/schemas/PublicSavedSearch"},"used_credits":{"type":"number","nullable":true,"example":0.01},"remaining_credits":{"type":"number","example":1199.99}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Saved search not found."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Saved search not found."}}}}}}},"tags":["Public API v1 — Saved Searches"]},"put":{"summary":"Update a saved search.\n\nRequires a Public API token with scope: searches:write.","operationId":"updateSavedSearch","description":"","parameters":[{"in":"path","name":"search_id","description":"The saved search ID.","example":42,"required":true,"schema":{"type":"integer","description":"The saved search ID.","example":42}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicSavedSearchInput"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"message":{"type":"string","example":"Saved search updated successfully."},"data":{"$ref":"#/components/schemas/PublicSavedSearch"},"used_credits":{"type":"number","nullable":true,"example":0.02},"remaining_credits":{"type":"number","example":1199.98}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Saved search not found."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Saved search not found."}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"The name field is required."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"The name field is required."}}}}}},"500":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Unable to update saved search."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Unable to update saved search."}}}}}}},"tags":["Public API v1 — Saved Searches"]},"delete":{"summary":"Delete a saved search.\n\nRequires a Public API token with scope: searches:write.","operationId":"deleteSavedSearch","description":"","parameters":[{"in":"path","name":"search_id","description":"The saved search ID to delete.","example":42,"required":true,"schema":{"type":"integer","description":"The saved search ID to delete.","example":42}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"message":{"type":"string","example":"Saved search deleted successfully."},"used_credits":{"type":"number","nullable":true,"example":0.01},"remaining_credits":{"type":"number","example":1199.99}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Saved search not found."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Saved search not found."}}}}}},"500":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Unable to delete saved search."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Unable to delete saved search."}}}}}}},"tags":["Public API v1 — Saved Searches"]}},"/api/v1/creative-categories":{"get":{"summary":"List active creative categories for the Explore `creative_categories` filter.\n\nRequires a Public API token with scope: explore:read.","operationId":"listCreativeCategories","description":"Returns the `{id, title}` pairs a caller passes as numeric IDs to `GET /api/v1/explore?creative_categories=<id>`. Bounded taxonomy lookup; charges no credits.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicCreativeCategory"}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/MissingScope"}},"tags":["Public API v1 — Explore"]}},"/api/v1/brand-profiles":{"get":{"summary":"List the workspace's brand profiles.\n\nRequires a Public API token with scope: brand-assets:read.","operationId":"listBrandProfiles","description":"Paginated, newest first by default. Billed per returned item.","parameters":[{"in":"query","name":"page","description":"The page number.","example":1,"required":false,"schema":{"type":"integer","example":1}},{"in":"query","name":"per_page","description":"Number of items per page (1-100). Defaults to 20. `limit` is accepted as an alias.","example":20,"required":false,"schema":{"type":"integer","example":20}},{"in":"query","name":"search","description":"Case-insensitive partial match on the brand profile name.","example":"acme","required":false,"schema":{"type":"string","example":"acme"}},{"in":"query","name":"sort_direction","description":"Sort direction on `created_at`. Defaults to `desc`.","example":"desc","required":false,"schema":{"type":"string","enum":["asc","desc"],"example":"desc"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicBrandProfile"}},"meta":{"allOf":[{"$ref":"#/components/schemas/PaginationMeta"},{"type":"object","properties":{"result_count":{"type":"integer","example":20}}}]},"used_credits":{"type":"number","nullable":true,"example":0.2},"remaining_credits":{"type":"number","nullable":true,"example":1199.8}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"403":{"$ref":"#/components/responses/MissingScope"}},"tags":["Public API v1 — Brand Assets"]},"post":{"summary":"Create a brand profile.\n\nRequires a Public API token with scope: brand-assets:write.","operationId":"createBrandProfile","description":"","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicBrandProfileCreateInput"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"data":{"$ref":"#/components/schemas/PublicBrandProfile"},"message":{"type":"string","example":"Brand profile created."},"used_credits":{"type":"number","nullable":true,"example":0.05},"remaining_credits":{"type":"number","nullable":true,"example":1199.95}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"403":{"$ref":"#/components/responses/MissingScope"},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"The name field is required."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"The name field is required."}}}}}}},"tags":["Public API v1 — Brand Assets"]}},"/api/v1/brand-profiles/{brand_profile_id}":{"get":{"summary":"Get a brand profile by ID.\n\nRequires a Public API token with scope: brand-assets:read.","operationId":"getBrandProfile","description":"Flat 1-unit read charge regardless of how many related products or assets are embedded.","parameters":[{"in":"path","name":"brand_profile_id","description":"The brand profile ID.","example":42,"required":true,"schema":{"type":"integer","description":"The brand profile ID.","example":42}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"data":{"$ref":"#/components/schemas/PublicBrandProfile"},"meta":{"type":"object","properties":{"result_count":{"type":"integer","example":1}}},"used_credits":{"type":"number","nullable":true,"example":0.01},"remaining_credits":{"type":"number","nullable":true,"example":1199.99}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"403":{"$ref":"#/components/responses/MissingScope"},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Brand profile not found."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Brand profile not found."}}}}}}},"tags":["Public API v1 — Brand Assets"]},"put":{"summary":"Update a brand profile.\n\nRequires a Public API token with scope: brand-assets:write.","operationId":"updateBrandProfile","description":"Partial update: only the supplied fields are validated and written.","parameters":[{"in":"path","name":"brand_profile_id","description":"The brand profile ID.","example":42,"required":true,"schema":{"type":"integer","description":"The brand profile ID.","example":42}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicBrandProfileUpdateInput"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"data":{"$ref":"#/components/schemas/PublicBrandProfile"},"used_credits":{"type":"number","nullable":true,"example":0.02},"remaining_credits":{"type":"number","nullable":true,"example":1199.98}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"403":{"$ref":"#/components/responses/MissingScope"},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Brand profile not found."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Brand profile not found."}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"The website link field must be a valid URL."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"The website link field must be a valid URL."}}}}}}},"tags":["Public API v1 — Brand Assets"]},"delete":{"summary":"Delete a brand profile.\n\nRequires a Public API token with scope: brand-assets:write.","operationId":"deleteBrandProfile","description":"Also removes the profile's stored logo. Products linked to the profile are detached, not deleted.","parameters":[{"in":"path","name":"brand_profile_id","description":"The brand profile ID.","example":42,"required":true,"schema":{"type":"integer","description":"The brand profile ID.","example":42}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"message":{"type":"string","example":"Brand profile deleted."},"used_credits":{"type":"number","nullable":true,"example":0.01},"remaining_credits":{"type":"number","nullable":true,"example":1199.99}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"403":{"$ref":"#/components/responses/MissingScope"},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Brand profile not found."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Brand profile not found."}}}}}}},"tags":["Public API v1 — Brand Assets"]}},"/api/v1/products":{"get":{"summary":"List the workspace's products.\n\nRequires a Public API token with scope: brand-assets:read.","operationId":"listProducts","description":"Paginated, newest first by default. Billed per returned item.","parameters":[{"in":"query","name":"page","description":"The page number.","example":1,"required":false,"schema":{"type":"integer","example":1}},{"in":"query","name":"per_page","description":"Number of items per page (1-100). Defaults to 20. `limit` is accepted as an alias.","example":20,"required":false,"schema":{"type":"integer","example":20}},{"in":"query","name":"search","description":"Case-insensitive partial match on the product title.","example":"serum","required":false,"schema":{"type":"string","example":"serum"}},{"in":"query","name":"brand_profile_id","description":"Restrict the list to products linked to this brand profile.","example":42,"required":false,"schema":{"type":"integer","example":42}},{"in":"query","name":"sort_direction","description":"Sort direction on `created_at`. Defaults to `desc`.","example":"desc","required":false,"schema":{"type":"string","enum":["asc","desc"],"example":"desc"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicProduct"}},"meta":{"allOf":[{"$ref":"#/components/schemas/PaginationMeta"},{"type":"object","properties":{"result_count":{"type":"integer","example":20}}}]},"used_credits":{"type":"number","nullable":true,"example":0.2},"remaining_credits":{"type":"number","nullable":true,"example":1199.8}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"403":{"$ref":"#/components/responses/MissingScope"}},"tags":["Public API v1 — Products"]},"post":{"summary":"Create a product.\n\nRequires a Public API token with scope: brand-assets:write.","operationId":"createProduct","description":"`image_urls` are fetched server-side after the product row is written; only public http/https URLs are accepted.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicProductCreateInput"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"data":{"$ref":"#/components/schemas/PublicProduct"},"message":{"type":"string","example":"Product created."},"used_credits":{"type":"number","nullable":true,"example":0.05},"remaining_credits":{"type":"number","nullable":true,"example":1199.95}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"403":{"$ref":"#/components/responses/MissingScope"},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"The title field is required."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"The title field is required."}}}}}}},"tags":["Public API v1 — Products"]}},"/api/v1/products/{product_id}":{"get":{"summary":"Get a product by ID.\n\nRequires a Public API token with scope: brand-assets:read.","operationId":"getProduct","description":"Flat 1-unit read charge regardless of how many images or videos are embedded.","parameters":[{"in":"path","name":"product_id","description":"The product ID.","example":77,"required":true,"schema":{"type":"integer","description":"The product ID.","example":77}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"data":{"$ref":"#/components/schemas/PublicProduct"},"meta":{"type":"object","properties":{"result_count":{"type":"integer","example":1}}},"used_credits":{"type":"number","nullable":true,"example":0.01},"remaining_credits":{"type":"number","nullable":true,"example":1199.99}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"403":{"$ref":"#/components/responses/MissingScope"},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Product not found."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Product not found."}}}}}}},"tags":["Public API v1 — Products"]},"put":{"summary":"Update a product.\n\nRequires a Public API token with scope: brand-assets:write.","operationId":"updateProduct","description":"Partial update: only the supplied fields are validated and written.","parameters":[{"in":"path","name":"product_id","description":"The product ID.","example":77,"required":true,"schema":{"type":"integer","description":"The product ID.","example":77}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicProductUpdateInput"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"data":{"$ref":"#/components/schemas/PublicProduct"},"used_credits":{"type":"number","nullable":true,"example":0.02},"remaining_credits":{"type":"number","nullable":true,"example":1199.98}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"403":{"$ref":"#/components/responses/MissingScope"},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Product not found."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Product not found."}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"The title field must be a string."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"The title field must be a string."}}}}}}},"tags":["Public API v1 — Products"]},"delete":{"summary":"Delete a product.\n\nRequires a Public API token with scope: brand-assets:write.","operationId":"deleteProduct","description":"","parameters":[{"in":"path","name":"product_id","description":"The product ID.","example":77,"required":true,"schema":{"type":"integer","description":"The product ID.","example":77}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"message":{"type":"string","example":"Product deleted."},"used_credits":{"type":"number","nullable":true,"example":0.01},"remaining_credits":{"type":"number","nullable":true,"example":1199.99}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"403":{"$ref":"#/components/responses/MissingScope"},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Product not found."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Product not found."}}}}}}},"tags":["Public API v1 — Products"]}},"/api/v1/support-requests":{"post":{"summary":"Submit a support request on behalf of the workspace.\n\nRequires a Public API token with scope: support:write.","operationId":"submitSupportRequest","description":"Creates a tracked support ticket and returns its `tracking_reference`. Charges no credits by default.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicSupportRequestInput"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"data":{"$ref":"#/components/schemas/PublicSupportRequest"},"message":{"type":"string","example":"Support request submitted."}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"403":{"$ref":"#/components/responses/MissingScope"},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","description":"NOTE: this operation validates with `$request->validate()` on a plain `Request` (SupportRequestController::store), not a Public API FormRequest — so Laravel's default validation response renders here. `errors` is the field -> messages OBJECT, not the `errors: true` boolean the rest of this API returns. Normalising the controller would change a shipped response contract and is tracked separately.","example":{"message":"The selected category is invalid.","errors":{"category":["The selected category is invalid."]}},"properties":{"message":{"type":"string","example":"The selected category is invalid."},"errors":{"type":"object","description":"Field name -> array of validation messages.","additionalProperties":{"type":"array","items":{"type":"string"}}}}}}}}},"tags":["Public API v1 — Support"]}},"/api/v1/shops":{"get":{"tags":["Public API v1 — Shops"],"summary":"Search the GetHookd shop catalog.\n\nRequires a Public API token with scope: shops:read.","operationId":"listShops","description":"Paginated. Billed per returned shop (`cost.units_per_result`). **Environment-gated:** these operations are registered only where the shops surfaces are live (staging and local). They are not served in production, where `McpToolAvailability::shopsEnabled()` hard-returns false — see routes/api.php.","parameters":[{"in":"query","name":"page","description":"The page number.","required":false,"schema":{"type":"integer","example":1},"example":1},{"in":"query","name":"per_page","description":"Number of items per page (1-50). Defaults to 20.","required":false,"schema":{"type":"integer","minimum":1,"maximum":50,"example":20},"example":20},{"in":"query","name":"q","description":"Free-text match on shop name or domain.","required":false,"schema":{"type":"string","example":"supplements"},"example":"supplements"},{"in":"query","name":"country_codes","description":"Restrict to these ISO-3166 alpha-2 country codes.","required":false,"schema":{"type":"array","items":{"type":"string","example":"US"}},"example":["US"]},{"in":"query","name":"niche_ids","description":"Restrict to these parent-category (niche) ids.","required":false,"schema":{"type":"array","items":{"type":"integer","example":5}},"example":[5]},{"in":"query","name":"min_monthly_visits","description":"Lower bound on estimated monthly visits.","required":false,"schema":{"type":"integer","example":10000},"example":10000},{"in":"query","name":"max_monthly_visits","description":"Upper bound on estimated monthly visits.","required":false,"schema":{"type":"integer","example":500000},"example":500000},{"in":"query","name":"shopify_detected","description":"Restrict to shops detected as running Shopify.","required":false,"schema":{"type":"boolean","example":true},"example":true},{"in":"query","name":"sort","description":"Sort key. One of the values accepted by `ShopListRequest::SORT_OPTIONS`.","required":false,"schema":{"type":"string","enum":["most_relevant","relevance","most_monthly_visits","monthly_visits","fastest_growing","most_products","newest_shops","newest","most_live_ads","shop","name","country","visits","growth","active_ads_growth","trustpilot","products","live_ads"],"example":"most_monthly_visits"},"example":"most_monthly_visits"},{"in":"query","name":"direction","description":"Sort direction. Defaults to `desc`.","required":false,"schema":{"type":"string","enum":["asc","desc"],"example":"desc"},"example":"desc"},{"in":"query","name":"search","description":"Alias of `q`.","required":false,"schema":{"type":"string"}},{"in":"query","name":"search_image","description":"Image URL to search by.","required":false,"schema":{"type":"string"}},{"in":"query","name":"canonical_country_codes","description":"Comma-separated canonical ISO country codes.","required":false,"schema":{"type":"string"}},{"in":"query","name":"languages","description":"Comma-separated language codes.","required":false,"schema":{"type":"string"}},{"in":"query","name":"currencies","description":"Comma-separated currency codes.","required":false,"schema":{"type":"string"}},{"in":"query","name":"shopify_plans","description":"Comma-separated Shopify plan names.","required":false,"schema":{"type":"string"}},{"in":"query","name":"shopify_plan_group","description":"Shopify plan group.","required":false,"schema":{"type":"string"}},{"in":"query","name":"shopify_themes","description":"Comma-separated Shopify theme names.","required":false,"schema":{"type":"string"}},{"in":"query","name":"shopify_theme","description":"Single Shopify theme name.","required":false,"schema":{"type":"string"}},{"in":"query","name":"shopify_apps","description":"Comma-separated Shopify app names.","required":false,"schema":{"type":"string"}},{"in":"query","name":"pixels","description":"Comma-separated tracking pixels.","required":false,"schema":{"type":"string"}},{"in":"query","name":"display_formats","description":"Comma-separated display formats.","required":false,"schema":{"type":"string"}},{"in":"query","name":"visitor_country_codes","description":"Comma-separated visitor ISO country codes.","required":false,"schema":{"type":"string"}},{"in":"query","name":"visitor_countries","description":"Alias of `visitor_country_codes`.","required":false,"schema":{"type":"string"}},{"in":"query","name":"shop_created_from","description":"Earliest shop creation date (Y-m-d).","required":false,"schema":{"type":"string"}},{"in":"query","name":"shop_created_to","description":"Latest shop creation date (Y-m-d).","required":false,"schema":{"type":"string"}},{"in":"query","name":"shop_created_kinds","description":"Comma-separated shop-creation precision kinds.","required":false,"schema":{"type":"string"}},{"in":"query","name":"min_traffic_growth_pct","description":"Minimum traffic growth percentage.","required":false,"schema":{"type":"number"}},{"in":"query","name":"max_traffic_growth_pct","description":"Maximum traffic growth percentage.","required":false,"schema":{"type":"number"}},{"in":"query","name":"min_active_ads_growth_pct","description":"Minimum active-ad growth percentage.","required":false,"schema":{"type":"number"}},{"in":"query","name":"max_active_ads_growth_pct","description":"Maximum active-ad growth percentage.","required":false,"schema":{"type":"number"}},{"in":"query","name":"min_product_count","description":"Minimum listable product count.","required":false,"schema":{"type":"integer"}},{"in":"query","name":"max_product_count","description":"Maximum listable product count.","required":false,"schema":{"type":"integer"}},{"in":"query","name":"min_live_ads_count","description":"Minimum live ad count.","required":false,"schema":{"type":"integer"}},{"in":"query","name":"max_live_ads_count","description":"Maximum live ad count.","required":false,"schema":{"type":"integer"}},{"in":"query","name":"min_trustpilot_rating","description":"Minimum Trustpilot rating.","required":false,"schema":{"type":"number"}},{"in":"query","name":"max_trustpilot_rating","description":"Maximum Trustpilot rating.","required":false,"schema":{"type":"number"}},{"in":"query","name":"min_trustpilot_review_count","description":"Minimum Trustpilot review count.","required":false,"schema":{"type":"integer"}},{"in":"query","name":"max_trustpilot_review_count","description":"Maximum Trustpilot review count.","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicShop"}},"used_credits":{"type":"number","description":"Credits charged for this call.","example":0.04},"remaining_credits":{"type":"number","description":"Workspace credit balance after the charge.","example":199.96},"meta":{"allOf":[{"$ref":"#/components/schemas/PaginationMeta"},{"type":"object","properties":{"result_count":{"type":"integer","example":20}}}]}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"403":{"description":"Missing the `shops:read` scope, OR the workspace is authenticated and in scope but not entitled to Shops. The two envelopes differ — the entitlement denial carries `data.reason` and upsell metadata.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/MissingScopeError"},{"$ref":"#/components/schemas/ShopsEntitlementDenied"}]}}}},"404":{"description":"The token's workspace has no owner row, so no entitlement can be resolved for it.","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Workspace owner not found."}}}}},"422":{"description":"Query parameter validation failed. Validation runs AFTER the entitlement gate, so a caller without the Shops entitlement sees 403 rather than 422.","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"The given data was invalid."}}}}}}}},"/api/v1/shops/{shop_id}":{"get":{"tags":["Public API v1 — Shops"],"summary":"Fetch one shop by id.\n\nRequires a Public API token with scope: shops:read.","operationId":"getShop","description":"Billed as a single result (`meta.result_count = 1`). **Environment-gated:** these operations are registered only where the shops surfaces are live (staging and local). They are not served in production, where `McpToolAvailability::shopsEnabled()` hard-returns false — see routes/api.php.","parameters":[{"in":"path","name":"shop_id","description":"The shop id.","required":true,"schema":{"type":"integer","example":1234},"example":1234}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":false},"data":{"$ref":"#/components/schemas/PublicShopDetail"},"used_credits":{"type":"number","description":"Credits charged for this call.","example":0.02},"remaining_credits":{"type":"number","description":"Workspace credit balance after the charge.","example":199.98},"meta":{"type":"object","properties":{"result_count":{"type":"integer","example":1}}}}}}}},"401":{"$ref":"#/components/responses/Unauthenticated"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"403":{"description":"Missing the `shops:read` scope, OR the workspace is authenticated and in scope but not entitled to Shops. The two envelopes differ — the entitlement denial carries `data.reason` and upsell metadata.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/MissingScopeError"},{"$ref":"#/components/schemas/ShopsEntitlementDenied"}]}}}},"404":{"description":"No shop with that id is visible to this workspace. Also returned when the token's workspace has no owner row.","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Shop not found."}}}}}}}}},"tags":[{"name":"Public API v1 — Auth","description":"Validate a Public API token and inspect the authenticated workspace and its granted scopes."},{"name":"Public API v1 — Explore","description":"Search and filter the ad library, look up individual ads, and list the reference values (page types, creative categories) used by Explore filters."},{"name":"Public API v1 — Brands","description":"Search and look up brands in the global brands catalog. Read-only; brand metadata is not billable."},{"name":"Public API v1 — BrandSpy","description":"Track brands with BrandSpy, list and manage the workspace's spied brands, and read a spied brand's ads including its currently-running top performers."},{"name":"Public API v1 — Swipe File","description":"Save ads to the workspace's swipe file, list saved ads, and remove them."},{"name":"Public API v1 — Boards","description":"Create and manage boards, and add or remove ads from them."},{"name":"Public API v1 — Saved Searches","description":"Create, list, update, and delete saved Explore searches."},{"name":"Public API v1 — Clone Ads","description":"Generate AI image variations from an existing ad, and list, fetch, or delete clone ads."},{"name":"Public API v1 — Brand Assets","description":"Create and manage the workspace's brand profiles — reusable brand identity used across other features."},{"name":"Public API v1 — Products","description":"Create and manage the workspace's products."},{"name":"Public API v1 — Support","description":"Submit a support request on behalf of the workspace."},{"name":"Public API v1 — Shops","description":"Search the GetHookd shop catalog and fetch individual shops."}],"components":{"schemas":{"MissingScopeError":{"type":"object","properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","description":"Names the scope required by the requested operation."}},"description":"Scope-denial envelope, as served by the `MissingScope` response."},"ShopsEntitlementDenied":{"type":"object","description":"Entitlement denial: the token is authenticated and carries `shops:read`, but the workspace's plan does not include Shops. Distinguished from a scope denial by the `data` object.","properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Your plan does not include Shops."},"data":{"type":"object","properties":{"reason":{"type":"string","example":"feature_disabled"},"feature":{"type":"string","example":"shops"},"upsell":{"type":"boolean","example":true}}}}},"PublicShopDetail":{"type":"object","description":"Shop detail payload (`PublicShopDetailResource` = `ShopDetailResource` minus vendor keys). Deliberately NOT the listing shape: adds `updated_at`, `social_handles` and `technologies`, and omits the listing-preview fields (`niches`, `visitor_countries`, `top_ad_image_urls`, `top_product_image_urls`, and the three sparklines).","properties":{"id":{"type":"integer","example":1234},"name":{"type":"string","example":"Aparthy"},"domain":{"type":"string","example":"aparthy.com"},"logo_url":{"type":"string","nullable":true,"example":"https://cdn.gethookd.ai/shops/aparthy.png"},"tagline":{"type":"string","nullable":true},"country_code":{"type":"string","nullable":true,"example":"US"},"country_display_code":{"type":"string","nullable":true,"example":"US"},"country_kind":{"type":"string","nullable":true,"example":"resolved"},"country_confidence_score":{"type":"number","nullable":true,"example":0.92},"country_source":{"type":"string","nullable":true},"primary_language":{"type":"string","nullable":true,"example":"en"},"primary_language_kind":{"type":"string","nullable":true},"primary_language_confidence_score":{"type":"number","nullable":true},"primary_language_source":{"type":"string","nullable":true},"shop_created_at":{"type":"string","format":"date-time","nullable":true},"shop_created_kind":{"type":"string","nullable":true},"shop_created_precision":{"type":"string","nullable":true},"shop_created_confidence_score":{"type":"number","nullable":true},"shop_created_source":{"type":"string","nullable":true},"niche_id":{"type":"integer","nullable":true,"example":5},"currency_code":{"type":"string","nullable":true,"example":"USD"},"currency_kind":{"type":"string","nullable":true},"currency_confidence_score":{"type":"number","nullable":true},"currency_source":{"type":"string","nullable":true},"shopify_detected":{"type":"boolean","example":true},"shopify_plan":{"type":"string","nullable":true},"trustpilot_rating":{"type":"string","nullable":true,"example":"4.30"},"trustpilot_review_count":{"type":"integer","nullable":true,"example":812},"monthly_visits":{"type":"integer","nullable":true,"example":145000},"traffic_growth_pct":{"type":"string","nullable":true,"example":"12.40"},"product_count":{"type":"integer","nullable":true,"example":248},"live_ads_count":{"type":"integer","example":37},"active_ads_growth_pct":{"type":"string","nullable":true,"example":"-4.10"},"updated_at":{"type":"string","format":"date-time","nullable":true,"description":"Last time the shop record was updated."},"social_handles":{"type":"object","nullable":true,"description":"Social profile handles keyed by network.","additionalProperties":{"type":"string"}},"technologies":{"type":"array","nullable":true,"description":"Detected storefront technologies.","items":{"type":"string"}}}},"PublicShop":{"type":"object","description":"A shop in the GetHookd catalog. Upstream-vendor blocks (`trendtrack_profile`, `traffic_source`) are stripped from every Public API v1 shop payload — see App\\Http\\Resources\\PublicApi\\V1\\PublicShopResource::VENDOR_KEYS.","properties":{"id":{"type":"integer","example":1234},"name":{"type":"string","example":"Aparthy"},"domain":{"type":"string","example":"aparthy.com"},"logo_url":{"type":"string","nullable":true,"example":"https://cdn.gethookd.ai/shops/aparthy.png"},"tagline":{"type":"string","nullable":true},"country_code":{"type":"string","nullable":true,"example":"US"},"country_display_code":{"type":"string","nullable":true,"example":"US"},"country_kind":{"type":"string","nullable":true,"example":"resolved"},"country_confidence_score":{"type":"number","nullable":true,"example":0.92},"country_source":{"type":"string","nullable":true},"primary_language":{"type":"string","nullable":true,"example":"en"},"primary_language_kind":{"type":"string","nullable":true},"primary_language_confidence_score":{"type":"number","nullable":true},"primary_language_source":{"type":"string","nullable":true},"shop_created_at":{"type":"string","format":"date-time","nullable":true},"shop_created_kind":{"type":"string","nullable":true},"shop_created_precision":{"type":"string","nullable":true},"shop_created_confidence_score":{"type":"number","nullable":true},"shop_created_source":{"type":"string","nullable":true},"niche_id":{"type":"integer","nullable":true,"example":5},"niches":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":5},"title":{"type":"string","example":"Beauty"}}}},"currency_code":{"type":"string","nullable":true,"example":"USD"},"currency_kind":{"type":"string","nullable":true},"currency_confidence_score":{"type":"number","nullable":true},"currency_source":{"type":"string","nullable":true},"shopify_detected":{"type":"boolean","example":true},"shopify_plan":{"type":"string","nullable":true},"trustpilot_rating":{"type":"string","nullable":true,"example":"4.30"},"trustpilot_review_count":{"type":"integer","nullable":true,"example":812},"monthly_visits":{"type":"integer","nullable":true,"example":145000},"traffic_growth_pct":{"type":"string","nullable":true,"example":"12.40"},"product_count":{"type":"integer","nullable":true,"example":248},"live_ads_count":{"type":"integer","example":37},"active_ads_growth_pct":{"type":"string","nullable":true,"example":"-4.10"},"top_product_image_urls":{"type":"array","items":{"type":"string"}},"top_ad_image_urls":{"type":"array","items":{"type":"string"}},"traffic_sparkline":{"type":"array","items":{"type":"number"}},"live_ads_sparkline":{"type":"array","items":{"type":"number"}},"active_ads_sparkline":{"type":"array","items":{"type":"number"}},"visitor_countries":{"type":"array","items":{"type":"object","properties":{"country_code":{"type":"string","example":"US"},"avg_share_pct":{"type":"number","example":42.5}}}}}},"PaginationMeta":{"type":"object","properties":{"current_page":{"type":"integer","example":1},"per_page":{"type":"integer","example":20},"total":{"type":"integer","example":100},"last_page":{"type":"integer","example":5}}},"PublicAd":{"type":"object","properties":{"id":{"type":"integer","example":123},"external_id":{"type":"string","example":"9999999"},"platform":{"type":"string","example":"facebook"},"display_format":{"type":"string","example":"video"},"title":{"type":"string","nullable":true,"example":"Great offer"},"body":{"type":"string","nullable":true,"example":"Check out our latest deal..."},"landing_page":{"type":"string","nullable":true,"example":"https://example.com"},"page_type":{"type":"string","nullable":true,"description":"Landing-page type slug (see GET /api/v1/page-types). Null when unclassified.","example":"quiz_page"},"link_description":{"type":"string","nullable":true,"example":"Shop now"},"cta_type":{"type":"string","nullable":true,"example":"SHOP_NOW"},"cta_text":{"type":"string","nullable":true,"example":"Shop Now"},"start_date":{"type":"string","nullable":true,"example":"2025-01-05"},"end_date":{"type":"string","nullable":true,"example":null},"days_active":{"type":"integer","nullable":true,"example":21},"active_in_library":{"type":"integer","nullable":true,"example":1},"used_count":{"type":"integer","nullable":true,"example":4},"is_aaa_eligible":{"type":"integer","nullable":true,"example":1},"age_audience_min":{"type":"integer","nullable":true,"example":25},"age_audience_max":{"type":"integer","nullable":true,"example":34},"gender_audience":{"type":"string","nullable":true,"example":"All"},"eu_total_reach":{"type":"integer","nullable":true,"example":5000},"ad_spend_range_score":{"type":"integer","nullable":true,"example":3},"ad_spend_range_score_title":{"type":"string","nullable":true,"example":"$2,001 - $5,000"},"performance_score":{"type":"integer","nullable":true,"example":120},"performance_score_title":{"type":"string","nullable":true,"example":"Winning"},"share_url":{"type":"string","nullable":true,"example":"https://app.gethookd.ai/share/ad/123"},"brand":{"$ref":"#/components/schemas/PublicBrand"},"media":{"type":"array","items":{"$ref":"#/components/schemas/PublicAdMedia"}},"ad_cards":{"type":"array","items":{"$ref":"#/components/schemas/PublicAdCard"}}}},"PublicBrand":{"type":"object","properties":{"external_id":{"type":"string","example":"2016485295279615"},"name":{"type":"string","example":"Acme"},"logo_url":{"type":"string","nullable":true,"example":"https://example.com/logo.png"},"active_ads":{"type":"integer","nullable":true,"example":109}}},"PublicAdMedia":{"type":"object","properties":{"type":{"type":"string","example":"video"},"url":{"type":"string","example":"https://example.com/video.mp4"},"resized_url":{"type":"string","nullable":true,"example":"https://example.com/video-resized.mp4"},"thumbnail_url":{"type":"string","nullable":true,"example":"https://example.com/thumb.jpg"},"video_length":{"type":"integer","nullable":true,"example":30}}},"PublicAdCard":{"type":"object","properties":{"title":{"type":"string","nullable":true,"example":"Variant A"},"body":{"type":"string","nullable":true,"example":"Ad card body text"},"caption":{"type":"string","nullable":true,"example":"Caption text"},"cta_type":{"type":"string","nullable":true,"example":"SHOP_NOW"},"cta_text":{"type":"string","nullable":true,"example":"Shop Now"},"landing_page":{"type":"string","nullable":true,"example":"https://example.com/variant-a"},"media":{"type":"array","items":{"$ref":"#/components/schemas/PublicAdMedia"}}}},"PublicSpiedBrand":{"type":"object","properties":{"id":{"type":"integer","example":1},"brand_id":{"type":"integer","example":456},"brand_external_id":{"type":"string","example":"2016485295279615"},"brand_name":{"type":"string","nullable":true,"example":"Acme"},"brand_logo_url":{"type":"string","nullable":true,"example":"https://example.com/logo.png"},"status":{"type":"string","example":"1"},"active_ads":{"type":"integer","example":109},"inactive_ads":{"type":"integer","example":23},"videos":{"type":"integer","example":45},"images":{"type":"integer","example":64},"carousels":{"type":"integer","example":23},"created_at":{"type":"string","format":"date-time","example":"2025-01-15T10:30:00+00:00"},"last_spied_at":{"type":"string","format":"date-time","nullable":true,"example":"2025-03-01T14:00:00+00:00"}}},"PublicBoard":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"Top Performers"},"slug":{"type":"string","example":"top-performers-1"},"ad_count":{"type":"integer","example":25},"is_public":{"type":"boolean","example":true},"public_url":{"type":"string","nullable":true,"example":"https://app.gethookd.ai/share/board/top-performers-1"},"created_at":{"type":"string","format":"date-time","example":"2025-01-15T10:30:00+00:00"},"updated_at":{"type":"string","format":"date-time","example":"2025-03-01T14:00:00+00:00"}}},"PublicCloneAd":{"type":"object","properties":{"id":{"type":"integer","example":1},"title":{"type":"string","example":"Clone of Ad #123"},"created_at":{"type":"string","format":"date-time","example":"2025-03-01T14:00:00+00:00"},"updated_at":{"type":"string","format":"date-time","example":"2025-03-01T14:05:00+00:00"},"prompts":{"type":"array","items":{"$ref":"#/components/schemas/PublicCloneAdPrompt"}}}},"PublicCloneAdPrompt":{"type":"object","properties":{"id":{"type":"integer","example":1},"prompt":{"type":"string","example":"Clone ad #123"},"aspect_ratio":{"type":"string","example":"Square"},"in_progress":{"type":"boolean","example":false},"created_at":{"type":"string","format":"date-time","example":"2025-03-01T14:00:00+00:00"},"media":{"type":"array","items":{"$ref":"#/components/schemas/PublicCloneAdMedia"}}}},"PublicCloneAdMedia":{"type":"object","properties":{"id":{"type":"integer","example":1},"url":{"type":"string","example":"https://example.com/generated-image.png"},"aspect_ratio":{"type":"string","example":"Square"},"is_image_reference":{"type":"boolean","example":false},"order":{"type":"integer","example":1}}},"PublicSavedSearch":{"type":"object","properties":{"id":{"type":"integer","example":42},"user_id":{"type":"integer","example":7},"name":{"type":"string","example":"US winning supplements"},"is_predefine":{"type":"boolean","nullable":true,"example":false},"gender_audience":{"type":"string","nullable":true,"example":"men,women"},"age_audience":{"type":"string","nullable":true,"example":"25-34,35-44"},"ad_spend_range":{"type":"string","nullable":true,"example":"3,4"},"ads_per_brand_limit":{"type":"integer","nullable":true,"example":4},"created_at":{"type":"string","format":"date-time","example":"2026-05-01T10:30:00+00:00"},"updated_at":{"type":"string","format":"date-time","example":"2026-05-01T10:30:00+00:00"},"parent_categories":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":16},"title":{"type":"string","example":"Health/Wellness"}}}},"languages":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"English"}}}},"excluded_brands":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":10},"name":{"type":"string","example":"Acme"}}}},"creative_categories":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":2},"title":{"type":"string","example":"Testimonial - Reviews"}}}}}},"PublicSavedSearchInput":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"The saved search name. Max 120 characters.","example":"US winning supplements","maxLength":120},"parent_categories":{"type":"array","description":"Niche / parent-category IDs. Max 50 items.","maxItems":50,"items":{"type":"integer"},"example":[16,30]},"languages":{"type":"array","description":"Language IDs. Max 20 items.","maxItems":20,"items":{"type":"integer"},"example":[1]},"excluded_brands":{"type":"array","description":"Brand IDs to exclude. Max 100 items.","maxItems":100,"items":{"type":"integer"},"example":[10,11]},"creative_categories":{"type":"array","description":"Creative-category IDs. Max 50 items.","maxItems":50,"items":{"type":"integer"},"example":[2,17]},"gender_audience":{"type":"string","description":"Gender audience filter (CSV). Max 200 characters.","example":"men,women","maxLength":200,"nullable":true},"age_audience":{"type":"string","description":"Age audience filter (CSV). Max 200 characters.","example":"25-34,35-44","maxLength":200,"nullable":true},"ad_spend_range":{"type":"string","description":"Spend buckets (CSV). Max 200 characters.","example":"3,4","maxLength":200,"nullable":true},"ads_per_brand_limit":{"type":"integer","description":"Limit results per brand (1-50).","example":4,"minimum":1,"maximum":50,"nullable":true}}},"PublicCreativeCategory":{"type":"object","properties":{"id":{"type":"integer","example":2},"title":{"type":"string","example":"Testimonial - Reviews"}}},"PublicBrandProfileProperty":{"type":"object","required":["id","brand_profile_id","type","value","sort_order","created_at","updated_at"],"properties":{"id":{"type":"integer","example":501},"brand_profile_id":{"type":"integer","example":42},"type":{"type":"string","example":"lifestyle"},"value":{"type":"string","example":"Urban professionals"},"sort_order":{"type":"integer","example":0},"created_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-01T10:30:00.000000Z"},"updated_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-01T10:30:00.000000Z"}}},"PublicBrandProfile":{"type":"object","properties":{"id":{"type":"integer","example":42},"name":{"type":"string","example":"Acme Skincare"},"product_information":{"type":"string","nullable":true,"example":"Vitamin C serum line."},"website_link":{"type":"string","nullable":true,"example":"https://acme.example.com"},"description_doc":{"type":"string","nullable":true,"example":"Long-form brand description."},"fonts":{"type":"string","nullable":true,"example":"Inter, Georgia"},"tagline":{"type":"string","nullable":true,"example":"Skin that shows up."},"mission_statement":{"type":"string","nullable":true,"example":"Clinical skincare without the markup."},"additional_information":{"type":"string","nullable":true,"example":"Cruelty-free, EU-manufactured."},"icp_age":{"type":"string","nullable":true,"example":"25-44"},"icp_location":{"type":"string","nullable":true,"example":"United States"},"icp_gender":{"type":"string","nullable":true,"example":"women"},"properties":{"type":"array","description":"Only present when the relation is loaded.","items":{"type":"object","properties":{"type":{"type":"string","example":"color"},"value":{"type":"string","example":"#0F172A"}}}},"web_properties":{"type":"array","description":"Only present when the relation is loaded.","items":{"type":"object","properties":{"name":{"type":"string","example":"Instagram"},"url":{"type":"string","example":"https://instagram.com/acme"}}}},"brand_assets":{"type":"array","description":"Only present when the relation is loaded.","items":{"type":"object","properties":{"asset_link":{"type":"string","example":"https://cdn.example.com/acme/logo.svg"}}}},"lifestyle":{"type":"array","description":"ICP lifestyle property records. Only present when the relation is loaded; the statement is in each item's `value` field.","items":{"$ref":"#/components/schemas/PublicBrandProfileProperty"}},"pain_points":{"type":"array","description":"ICP pain-point property records. Only present when the relation is loaded; the statement is in each item's `value` field.","items":{"$ref":"#/components/schemas/PublicBrandProfileProperty"}},"buying_habits":{"type":"array","description":"ICP buying-habit property records. Only present when the relation is loaded; the statement is in each item's `value` field.","items":{"$ref":"#/components/schemas/PublicBrandProfileProperty"}},"products":{"type":"array","description":"Only present when the relation is loaded.","items":{"$ref":"#/components/schemas/PublicProduct"}},"profile_picture":{"type":"object","description":"The `url` is a short-lived signed capability URL — never a raw origin/CDN URL.","properties":{"disk":{"type":"string","nullable":true,"example":"gcs"},"path":{"type":"string","nullable":true,"example":"brand-profiles/42/logo.png"},"url":{"type":"string","nullable":true,"example":"https://app.gethookd.ai/api/v1/brand-profiles/42/logo?signature=..."}}},"created_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-01T10:30:00.000000Z"},"updated_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-01T10:30:00.000000Z"}}},"PublicBrandProfileInput":{"type":"object","description":"Fields accepted when creating or partially updating a brand profile.","properties":{"name":{"type":"string","maxLength":255,"example":"Acme Skincare"},"brief_id":{"type":"integer","nullable":true,"description":"Must belong to the calling workspace.","example":9},"website_link":{"type":"string","nullable":true,"description":"http/https only.","example":"https://acme.example.com"},"product_information":{"type":"string","nullable":true,"example":"Vitamin C serum line."},"description_doc":{"type":"string","nullable":true,"example":"Long-form brand description."},"fonts":{"type":"string","nullable":true,"maxLength":255,"example":"Inter, Georgia"},"tagline":{"type":"string","nullable":true,"maxLength":255,"example":"Skin that shows up."},"mission_statement":{"type":"string","nullable":true,"maxLength":255,"example":"Clinical skincare without the markup."},"additional_information":{"type":"string","nullable":true,"example":"Cruelty-free, EU-manufactured."},"icp_age":{"type":"string","nullable":true,"maxLength":255,"example":"25-44"},"icp_location":{"type":"string","nullable":true,"maxLength":255,"example":"United States"},"icp_gender":{"type":"string","nullable":true,"maxLength":255,"example":"women"},"properties":{"type":"array","description":"Max 50 items.","maxItems":50,"items":{"type":"object","required":["type","value"],"properties":{"type":{"type":"string","enum":["color","personality","music"],"example":"color"},"value":{"type":"string","maxLength":255,"example":"#0F172A"}}}},"web_properties":{"type":"array","description":"Max 20 items.","maxItems":20,"items":{"type":"object","required":["name","url"],"properties":{"name":{"type":"string","maxLength":255,"example":"Instagram"},"url":{"type":"string","maxLength":2048,"example":"https://instagram.com/acme"}}}},"brand_assets":{"type":"array","description":"Max 20 items.","maxItems":20,"items":{"type":"object","required":["asset_link"],"properties":{"asset_link":{"type":"string","maxLength":255,"example":"https://cdn.example.com/acme/logo.svg"}}}},"lifestyle":{"type":"array","description":"Max 50 items.","maxItems":50,"items":{"type":"string","maxLength":255},"example":["Urban professionals"]},"buying_habits":{"type":"array","description":"Max 50 items.","maxItems":50,"items":{"type":"string","maxLength":255},"example":["Subscribes to refills"]},"pain_points":{"type":"array","description":"Max 50 items.","maxItems":50,"items":{"type":"string","maxLength":255},"example":["Dull skin after 30"]},"product_ids":{"type":"array","description":"Product IDs to attach. Must belong to the calling workspace. Max 100 items.","maxItems":100,"items":{"type":"integer"},"example":[77,78]}}},"PublicBrandProfileCreateInput":{"allOf":[{"$ref":"#/components/schemas/PublicBrandProfileInput"},{"type":"object","required":["name"]}]},"PublicBrandProfileUpdateInput":{"allOf":[{"$ref":"#/components/schemas/PublicBrandProfileInput"}],"description":"Partial update. Only supplied fields are validated and written."},"PublicProduct":{"type":"object","properties":{"id":{"type":"integer","example":77},"title":{"type":"string","example":"Vitamin C Serum"},"description":{"type":"string","nullable":true,"example":"15% L-ascorbic acid, fragrance free."},"target_audience":{"type":"string","nullable":true,"example":"Women 25-44 in the US"},"company_name":{"type":"string","nullable":true,"example":"Acme Skincare"},"ad_objective":{"type":"string","nullable":true,"example":"conversions"},"usp":{"type":"string","nullable":true,"example":"Stabilised formula that does not oxidise."},"tone_of_voice":{"type":"string","nullable":true,"example":"clinical, warm"},"platform":{"type":"string","nullable":true,"example":"facebook"},"awareness_level":{"type":"string","nullable":true,"example":"problem-aware"},"market_sophistication":{"type":"string","nullable":true,"example":"stage 3"},"language":{"type":"string","nullable":true,"example":"en"},"brand_profile_id":{"type":"integer","nullable":true,"example":42},"brand_profile":{"type":"object","nullable":true,"description":"Only present when the relation is loaded. `logo_url` is a short-lived signed capability URL.","properties":{"id":{"type":"integer","example":42},"name":{"type":"string","example":"Acme Skincare"},"logo_url":{"type":"string","nullable":true,"example":"https://app.gethookd.ai/api/v1/brand-profiles/42/logo?signature=..."}}},"images":{"type":"array","description":"Only present when the relation is loaded.","items":{"type":"object"}},"videos":{"type":"array","description":"Only present when the relation is loaded.","items":{"type":"object"}},"scraped_url":{"type":"string","nullable":true,"example":"https://acme.example.com/products/vitamin-c"},"scraped_data":{"type":"object","nullable":true},"competitors":{"type":"array","nullable":true,"items":{"$ref":"#/components/schemas/PublicProductCompetitor"},"example":[{"name":"Brand B","positioning":"Clinical alternative"}]},"pricing_positioning":{"type":"string","nullable":true,"example":"premium"},"differentiators":{"type":"array","nullable":true,"items":{"type":"string"},"example":["Airless pump"]},"emotional_triggers":{"type":"array","nullable":true,"items":{"type":"string"},"example":["Confidence"]},"market_hooks":{"type":"array","nullable":true,"items":{"type":"string"},"example":["Dermatologist tested"]},"deep_research_completed_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-01T10:30:00.000000Z"},"created_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-01T10:30:00.000000Z"},"updated_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-01T10:30:00.000000Z"}}},"PublicProductInput":{"type":"object","description":"Fields accepted when creating or partially updating a product.","properties":{"title":{"type":"string","maxLength":255,"example":"Vitamin C Serum"},"company_name":{"type":"string","nullable":true,"maxLength":255,"example":"Acme Skincare"},"description":{"type":"string","nullable":true,"maxLength":65000,"example":"15% L-ascorbic acid, fragrance free."},"target_audience":{"type":"string","nullable":true,"maxLength":2000,"example":"Women 25-44 in the US"},"ad_objective":{"type":"string","nullable":true,"maxLength":100,"example":"conversions"},"usp":{"type":"string","nullable":true,"maxLength":5000,"example":"Stabilised formula that does not oxidise."},"tone_of_voice":{"type":"string","nullable":true,"maxLength":100,"example":"clinical, warm"},"platform":{"type":"string","nullable":true,"maxLength":50,"example":"facebook"},"awareness_level":{"type":"string","nullable":true,"maxLength":100,"example":"problem-aware"},"market_sophistication":{"type":"string","nullable":true,"maxLength":100,"example":"stage 3"},"language":{"type":"string","nullable":true,"maxLength":50,"example":"en"},"brand_profile_id":{"type":"integer","nullable":true,"description":"Must belong to the calling workspace.","example":42},"image_urls":{"type":"array","description":"Public http/https image URLs to ingest. Max 10 items; internal/private hosts are rejected.","maxItems":10,"items":{"type":"string","maxLength":2048},"example":["https://cdn.example.com/acme/serum.jpg"]},"image_gcs_paths":{"type":"array","description":"Paths under `scraped-images/` returned by a previous scrape. Max 10 items.","maxItems":10,"items":{"type":"string","maxLength":500},"example":["scraped-images/acme/serum.jpg"]},"scraped_url":{"type":"string","nullable":true,"maxLength":2048,"example":"https://acme.example.com/products/vitamin-c"},"scraped_data":{"type":"object","nullable":true},"competitors":{"type":"array","maxItems":50,"items":{"$ref":"#/components/schemas/PublicProductCompetitor"},"example":[{"name":"Brand B","positioning":"Clinical alternative"}]},"differentiators":{"type":"array","maxItems":50,"items":{"type":"string","maxLength":2000},"example":["Airless pump"]},"emotional_triggers":{"type":"array","maxItems":50,"items":{"type":"string","maxLength":2000},"example":["Confidence"]},"market_hooks":{"type":"array","maxItems":50,"items":{"type":"string","maxLength":2000},"example":["Dermatologist tested"]},"pricing_positioning":{"type":"string","nullable":true,"maxLength":2000,"example":"premium"}}},"PublicProductCreateInput":{"allOf":[{"$ref":"#/components/schemas/PublicProductInput"},{"type":"object","required":["title"]}]},"PublicProductUpdateInput":{"allOf":[{"$ref":"#/components/schemas/PublicProductInput"}],"description":"Partial update. Only supplied fields are validated and written."},"PublicProductCompetitor":{"description":"A competitor may be stored as a legacy name string or as a researched object with positioning context.","oneOf":[{"type":"string","maxLength":2000,"example":"Brand B"},{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":255,"example":"Brand B"},"positioning":{"type":"string","nullable":true,"maxLength":2000,"example":"Clinical alternative"}},"additionalProperties":true}]},"PublicSupportRequest":{"type":"object","properties":{"id":{"type":"integer","example":512},"tracking_reference":{"type":"string","example":"SR-2026-000512"},"category":{"type":"string","enum":["feature_request","bug_report","billing_inquiry"],"example":"billing_inquiry"},"priority":{"type":"string","enum":["low","normal","high","urgent"],"nullable":true,"example":"normal"},"status":{"type":"string","example":"open"},"subject":{"type":"string","example":"Credits not refreshed after upgrade"},"contact_email":{"type":"string","nullable":true,"example":"ops@acme.example.com"},"context_url":{"type":"string","nullable":true,"example":"https://app.gethookd.ai/settings/billing"},"submitted_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-05-01T10:30:00+00:00"}}},"PublicSupportRequestInput":{"type":"object","required":["category","subject","message"],"properties":{"category":{"type":"string","enum":["feature_request","bug_report","billing_inquiry"],"description":"One of the categories accepted by the support intake.","example":"billing_inquiry"},"subject":{"type":"string","minLength":3,"maxLength":180,"example":"Credits not refreshed after upgrade"},"message":{"type":"string","minLength":10,"maxLength":5000,"example":"We upgraded on the 1st but the credit balance still shows the old plan."},"priority":{"type":"string","enum":["low","normal","high","urgent"],"nullable":true,"example":"normal"},"contact_email":{"type":"string","format":"email","nullable":true,"maxLength":255,"example":"ops@acme.example.com"},"context_url":{"type":"string","nullable":true,"maxLength":2048,"example":"https://app.gethookd.ai/settings/billing"},"metadata":{"type":"object","nullable":true,"description":"Free-form context forwarded to the support ticket."}}}},"responses":{"MissingScope":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","description":"Names the scope required by the requested operation."}}}}}},"Unauthenticated":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Unauthenticated."},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Unauthenticated."}}}}}},"InsufficientCredits":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"errors":true,"message":"Not enough credits"},"properties":{"errors":{"type":"boolean","example":true},"message":{"type":"string","example":"Not enough credits"}}}}}}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Public API team token, passed as `Authorization: Bearer <token>`. Each endpoint additionally requires the token to carry the scope named in its summary (e.g. explore:read, brand-spy:write, swipe-file:write, boards:write, searches:write, clone-ads:write)."}}}}