{"openapi":"3.1.0","info":{"title":"Reacher Data API","description":"Access your Reacher affiliate data programmatically. Use your API key in the x-api-key header and shop ID in the x-shop-id header.","version":"1.0.0"},"servers":[{"url":"/public/v1"},{"url":"https://api.reacherapp.com/public/v1","description":"Production"}],"paths":{"/shops":{"get":{"tags":["Shops"],"summary":"List your shops","description":"Returns all active shops accessible to this API key. Use the returned shop_id values in the x-shop-id header for other endpoints.","operationId":"list_shops_shops_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShopsListResponse"}}}}}}},"/creators/list":{"post":{"tags":["Creators"],"summary":"List creators","description":"List affiliate creators for your shop with pagination, sorting, and filtering. GMV represents the creator's lifetime revenue for this shop. Data updates approximately every hour.","operationId":"creators_list_creators_list_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorsListRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorsListPaginatedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/creators/performance":{"post":{"tags":["Creators"],"summary":"Creator performance (date-filtered)","description":"Top creators ranked by GMV earned within a specific date range. Unlike /creators/list (which shows lifetime GMV), this endpoint uses daily performance data and matches the dashboard's 'Top Creators' section exactly. Defaults to the last 30 days. Use this for period-specific rankings and reports.","operationId":"creators_performance_creators_performance_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorPerformanceRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorPerformanceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/creators/summary":{"post":{"tags":["Creators"],"summary":"Creator activity totals for a window","description":"Four headline totals for the shop over a date window: creators added, affiliate GMV earned, videos posted, and orders placed. Runs the same aggregate query that backs the summary cards above the portal's My Creators table, over the same trailing-28-day default window (TikTok reporting day, GMT-7).\n\n`gmv_generated` and `video_posts` are scoped to the shop's CRM-tracked creators, which makes them narrower than their whole-shop namesakes on POST /metrics/summary (`gmv`, `videos_posted`) — the two will not agree, by design.\n\nAggregates cover the whole shop — there is no filter parameter in v1. Defaults to the trailing 28 days. Requires a single shop.\n\nNo prior-period comparison is included; for a period-over-period delta, call twice with adjacent windows and subtract. Returns 503 when the aggregate times out — back off and retry rather than treating it as a failure.","operationId":"creators_summary","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorsSummaryRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorsSummaryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/creators/trends":{"post":{"tags":["Creators"],"summary":"Daily series per creator","description":"Daily time series for up to 100 named creators — affiliate GMV, video GMV, videos posted, video views, units sold, and commission. Backs sparklines and per-creator trend comparisons.\n\nEvery series is aligned positionally with the `days` axis and zero-filled, so index `i` of any array corresponds to `days[i]`. Creators with no activity in the window appear with all-zero arrays rather than being omitted; handles that don't exist in this shop are listed in `unresolved_handles` instead.\n\nDefaults to the trailing 28 days. Requires a single shop.","operationId":"creators_trends","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorTrendsRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorTrendsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/creators/levels":{"get":{"tags":["Creators"],"summary":"Creator distribution by GMV level","description":"Distribution of creators by GMV tier (L0-L7). Matches the dashboard's 'Creators by Level' pie chart. Uses active creators from the date range (defaults to last 30 days). Pass `start_date` and `end_date` query params (YYYY-MM-DD) to customize the window. L0=$0, L1=<$5k, L2=$5k-$25k, L3=$25k-$60k, L4=$60k-$150k, L5=$150k-$400k, L6=$400k-$1M, L7=>$1M.","operationId":"creator_levels_creators_levels_get","parameters":[{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"Start date (YYYY-MM-DD). Defaults to 30 days ago.","title":"Start Date"},"description":"Start date (YYYY-MM-DD). Defaults to 30 days ago."},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"description":"End date (YYYY-MM-DD). Defaults to the shop's newest settled day.","title":"End Date"},"description":"End date (YYYY-MM-DD). Defaults to the shop's newest settled day."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/creators/{creator_handle}":{"get":{"tags":["Creators"],"summary":"Get creator detail","description":"Get detailed information for a single creator, including groups, products, and latest 10 videos (for full video list, use POST /creators/{creator_handle}/videos).","operationId":"creator_detail_creators__creator_handle__get","parameters":[{"name":"creator_handle","in":"path","required":true,"schema":{"type":"string","title":"Creator Handle"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/creators/{creator_handle}/videos":{"post":{"tags":["Creators"],"summary":"List creator's videos","description":"Paginated list of videos by this creator in your shop.","operationId":"creator_videos_creators__creator_handle__videos_post","parameters":[{"name":"creator_handle","in":"path","required":true,"schema":{"type":"string","title":"Creator Handle"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorVideosRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideosListPaginatedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/creators/{creator_handle}/products":{"post":{"tags":["Creators"],"summary":"List creator's products","description":"Products this creator is affiliated with in your shop.","operationId":"creator_products_creators__creator_handle__products_post","parameters":[{"name":"creator_handle","in":"path","required":true,"schema":{"type":"string","title":"Creator Handle"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorProductsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorProductsPaginatedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/products/list":{"post":{"tags":["Products"],"summary":"List products","description":"Product performance metrics aggregated over a date range. Defaults to the last 7 days if no dates are specified. Data is typically 2-3 days behind the current date. Each product also carries its TikTok Seller Center per-product metrics as sc_* fields: all-channel GMV with its per-channel breakdown (affiliate, seller video, seller live, product card, shop tab), plus orders, units sold, customers, impressions, clicks, CTR, conversion, AOV, GMV per 1,000 impressions, add-to-cart, and refunds. The sc_* fields are null for products not yet on the Seller Center sync.","operationId":"products_list_products_list_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductsListRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductsListPaginatedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/products/{product_id}/creators":{"post":{"tags":["Products"],"summary":"List product's creators","description":"Creators affiliated with this product.","operationId":"product_creators_products__product_id__creators_post","parameters":[{"name":"product_id","in":"path","required":true,"schema":{"type":"string","title":"Product Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductCreatorsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductCreatorsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/products/{product_id}/videos":{"post":{"tags":["Products"],"summary":"List product's videos","description":"Videos featuring this product.","operationId":"product_videos_products__product_id__videos_post","parameters":[{"name":"product_id","in":"path","required":true,"schema":{"type":"string","title":"Product Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductVideosRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideosListPaginatedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/videos/list":{"post":{"tags":["Videos"],"summary":"List videos","description":"Affiliate videos and their performance metrics. Values represent lifetime totals per video (total views, total GMV, etc.). Data can be up to three days stale, given the delay in affiliate center.","operationId":"videos_list_videos_list_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideosListRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideosListPaginatedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/videos/creative":{"post":{"tags":["Videos"],"summary":"Top videos with creative breakdown","description":"The shop's top videos ranked by GMV earned in the date range (defaults to the last 30 days, same ranking as /videos/performance), each enriched with the AI creative breakdown: hook, sell points, product niche, shot style, and videography. Use this to analyze WHY the top videos perform — not just how much they earned. Videos that haven't been analyzed yet keep their metrics and list the unavailable breakdown fields in creative.missing_fields; render those as 'not analyzed' rather than blank. Multi-shop requests (x-shop-id: all or comma-separated) mirror /videos/performance's semantics: one ranking across shops with a single top-level currency (USD fallback) — use a single shop when per-shop currency precision matters.","operationId":"videos_creative_videos_creative_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideosCreativeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideosCreativeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/videos/performance":{"post":{"tags":["Videos"],"summary":"Video performance (date-filtered)","description":"Top videos ranked by GMV or views earned within a specific date range. Unlike /videos/list (which shows lifetime totals), this endpoint uses daily performance data and matches the dashboard's 'Top Videos' section exactly. Defaults to the last 30 days. Use this for period-specific rankings, trend analysis, and correlating video performance with product sales.","operationId":"videos_performance_videos_performance_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoPerformanceRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoPerformanceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/samples/list":{"post":{"tags":["Samples"],"summary":"List sample requests","description":"Track sample requests from creators in your shop. Shows only sample-related statuses. Data updates approximately every hour.\n\nEach row includes the requesting creator's `bio`, `categories`, and `email` (sourced from the same regional creators table the AI Creator Search API reads), so consumers can score sample requests on brand/niche fit without a second API call. These fields are `null` when the creator has no row in the regional creators table.","operationId":"samples_list_samples_list_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SamplesListRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SamplesListPaginatedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/samples/by-product":{"post":{"tags":["Samples"],"summary":"Sample metrics by product","description":"Sample performance breakdown by product. Shows requests, approvals, and GMV per product. Data can be up to three days stale, given the delay in affiliate center.","operationId":"samples_by_product_endpoint_samples_by_product_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SamplesByProductRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SamplesByProductPaginatedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/automations/list":{"post":{"tags":["Automations"],"summary":"List automations","description":"Your automations with performance and outreach metrics. Includes creators reached, sample requests, videos posted, and GMV. reply_rate is populated for Running, Stopped, and Completed automations.","operationId":"automations_list_automations_list_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationsListRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationsListPaginatedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/automations/filters":{"get":{"tags":["Automations"],"summary":"Region-specific filter options","description":"Returns the filter options (categories, GMV ranges, follower segments, engagement / view tiers, content-style options, etc.) valid for the given shop region. Use the response to drive filter dropdowns or to validate `creators_to_include.filters` values before submitting a create request.\n\nRegion differences:\n  * Numeric ranges use the region's currency (`£` for UK, `$` for US, `€` for EU regions, `R$` for BR, etc.).\n  * Category lists differ — UK has a smaller category set than US.\n  * AI-search filters (face_visibility / content_style / personality_tone / etc.) are only fully populated for US today; other regions surface a subset.\n  * Ethnicity filter is available globally regardless of region.\n\nSupported regions: `US`, `UK`, `IE`, `MX`, `DE`, `FR`, `ES`, `BR`, `IT`, `GLOBAL` (alias for US).","operationId":"automation_filters_automations_filters_get","parameters":[{"name":"shop_region","in":"query","required":true,"schema":{"type":"string","title":"Shop Region"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/automations/outreach-capacity":{"get":{"tags":["Automations"],"summary":"Outreach send capacity for this shop","description":"Current outreach send-capacity for a single shop, so callers can pre-check before sending TC invites / DMs and schedule retries when capacity frees.\n\nTwo independent limits govern outreach:\n* **Daily per-shop throttle** counted in **UTC** (resets at 00:00 UTC — see `daily_window_resets_at`). `daily_creators_used` counts actual sends only (skipped/`creator_not_found` invites do not count). The daily ceiling is plan/TikTok-dependent and is not returned as a single number; `daily_limit_throttled` flags when the shop is currently paused by it.\n* **TikTok's weekly outreach limit** (resets at the start of each week, enforced TikTok-side — the remaining count is not knowable to us). `weekly_limit_throttled` flags when paused by it; if true, retry next week.\n\nThe daily pool is **shared** between the automation engine and direct `POST /target-collabs` invites. Requires a single shop.","operationId":"automation_outreach_capacity_automations_outreach_capacity_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OutreachCapacityResponse"}}}}}}},"/automations/{automation_id}":{"get":{"tags":["Automations"],"summary":"Get automation detail","description":"Detailed automation data with performance metrics.","operationId":"automation_detail_automations__automation_id__get","parameters":[{"name":"automation_id","in":"path","required":true,"schema":{"type":"integer","title":"Automation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["Automations"],"summary":"Update automation","description":"Partial update across all 4 types. Cannot change automation_type, shop, lifecycle state, or system fields. `Idempotency-Key` optional; `X-Dry-Run: true` supported.","operationId":"automation_update","parameters":[{"name":"automation_id","in":"path","required":true,"schema":{"type":"integer","title":"Automation Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationWriteResponseV2"}}}},"400":{"description":"Validation, missing/invalid Idempotency-Key, or forbidden update field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"403":{"description":"API key lacks `read_write` scope (`WRITE_NOT_PERMITTED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"404":{"description":"Automation/list/group not in this shop.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"409":{"description":"Idempotency conflict, archived row, or email-account already linked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"429":{"description":"Write-tier rate limit (10/min, 50/hr, 100/day) exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Automations"],"summary":"Delete automation","description":"Soft-delete (state=archived). Reversible only by Reacher support. Calling on already-archived returns 200 with `already_archived: true`.","operationId":"automation_delete","parameters":[{"name":"automation_id","in":"path","required":true,"schema":{"type":"integer","title":"Automation Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationWriteResponseV2"}}}},"400":{"description":"Validation, missing/invalid Idempotency-Key, or forbidden update field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"403":{"description":"API key lacks `read_write` scope (`WRITE_NOT_PERMITTED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"404":{"description":"Automation/list/group not in this shop.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"409":{"description":"Idempotency conflict, archived row, or email-account already linked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"429":{"description":"Write-tier rate limit (10/min, 50/hr, 100/day) exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/automations/{automation_id}/creators":{"get":{"tags":["Automations"],"summary":"Per-creator outcome ledger","description":"Per-creator outcomes for ONE automation: which creators got an invite/DM submitted, which were skipped (and why), which were previously invited, etc. This is the authoritative per-creator answer to 'what happened to each invite' — backed by the always-present `target_collabs` / `Messages` / `skipped_creators` tables, so it stays populated even after the aggregate `creators_reached` / `skipped` counters on the list/detail responses age out (those read from a stats view that only retains recently-active automations).\n\n**Queued ≠ delivered.** An `invite_submitted` / `outreach_sent` outcome means Reacher's worker submitted the invite/DM through the same browser-automation path the portal uses. TikTok does not return a per-creator inbox-delivery receipt, so this is the most authoritative delivery signal available — not a confirmed inbox event. Requires a single shop.","operationId":"automation_creators_automations__automation_id__creators_get","parameters":[{"name":"automation_id","in":"path","required":true,"schema":{"type":"integer","title":"Automation Id"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Page Size"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationCreatorsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/automations/{automation_id}/messages":{"get":{"tags":["Automations"],"summary":"Automation message templates","description":"The message copy *configured* on one automation, flattened into a uniform shape regardless of automation type: the TC invite template (`tc_invite_message`), the ordered DM sequence (`message_sequence` — array order is send order), follow-up steps, and AI-chatbot Q&A. Placeholders (e.g. `{{creators username}}`) are NOT substituted — this is the template. For the exact per-creator text that was actually sent, use `GET /automations/{automation_id}/sent-messages`. Requires a single shop.","operationId":"automation_messages_automations__automation_id__messages_get","parameters":[{"name":"automation_id","in":"path","required":true,"schema":{"type":"integer","title":"Automation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationMessagesTemplateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/automations/{automation_id}/sent-messages":{"get":{"tags":["Automations"],"summary":"Exact sent message copy (grouped by creator)","description":"What this automation actually sent, with honest coverage semantics:\n\n* **`dm_sent`** — the exact rendered DM text per creator (placeholders substituted at send time), grouped by creator, most recently messaged first. Filter with `creator_handle`; page depth with `creator_limit` / `messages_per_creator`.\n* **`tc_sent`** — Target-Collab invitation batches (who was invited, when, status) plus the invite template. TikTok TC invites are sent from the template and the per-creator rendered text is not stored — `exact_copy: false` and `coverage_note` state this explicitly. When the automation is a campaign's TC automation, `campaign` carries the link.\n\nHybrid automations (Message + TC) populate both sections. Empty sections carry an explicit `message` instead of blank lists. Read-only. Requires a single shop.","operationId":"automation_sent_messages_automations__automation_id__sent_messages_get","parameters":[{"name":"automation_id","in":"path","required":true,"schema":{"type":"integer","title":"Automation Id"}},{"name":"creator_limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Creator Limit"}},{"name":"messages_per_creator","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"default":20,"title":"Messages Per Creator"}},{"name":"creator_handle","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Creator Handle"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationSentMessagesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/automations/target-collab":{"post":{"tags":["Automations"],"summary":"Create Target Collab automation","description":"Create a Target Collab (TC) invitation automation with optional follow-up DMs. Required: `read_write` scope, `Idempotency-Key` header. Pass `X-Dry-Run: true` to validate without persisting.","operationId":"automation_create_target_collab","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}},{"name":"X-Created-Via","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Created-Via"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationTargetCollabRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationWriteResponseV2"}}}},"400":{"description":"Validation, missing/invalid Idempotency-Key, or forbidden update field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"403":{"description":"API key lacks `read_write` scope (`WRITE_NOT_PERMITTED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"404":{"description":"Automation/list/group not in this shop.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"409":{"description":"Idempotency conflict, archived row, or email-account already linked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"429":{"description":"Write-tier rate limit (10/min, 50/hr, 100/day) exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/automations/tc-cleanup":{"post":{"tags":["Automations"],"summary":"Create TC Cleanup automation","description":"Create a TC Cleanup utility automation — re-targets creators in this shop who received a TC invite from a prior automation but didn't accept it. Conceptually closer to `sample_request` than to the proactive TC outreach types: products + creator selection only, no messages, no follow-ups. Required: `read_write` scope, `Idempotency-Key` header. Pass `X-Dry-Run: true` to validate without persisting.","operationId":"automation_create_tc_cleanup","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}},{"name":"X-Created-Via","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Created-Via"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationTcCleanupRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationWriteResponseV2"}}}},"400":{"description":"Validation, missing/invalid Idempotency-Key, or forbidden update field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"403":{"description":"API key lacks `read_write` scope (`WRITE_NOT_PERMITTED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"404":{"description":"Automation/list/group not in this shop.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"409":{"description":"Idempotency conflict, archived row, or email-account already linked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"429":{"description":"Write-tier rate limit (10/min, 50/hr, 100/day) exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/automations/dm":{"post":{"tags":["Automations"],"summary":"Create DM automation","description":"Create a Direct Message automation. The `mode` field selects between 4 variants: `vanilla` (text-only), `with_image`, `with_product_card`, `spark_code` (collect TikTok spark codes via form). Each mode has specific addon and config requirements — see model schema.","operationId":"automation_create_dm","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}},{"name":"X-Created-Via","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Created-Via"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationDmRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationWriteResponseV2"}}}},"400":{"description":"Validation, missing/invalid Idempotency-Key, or forbidden update field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"403":{"description":"API key lacks `read_write` scope (`WRITE_NOT_PERMITTED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"404":{"description":"Automation/list/group not in this shop.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"409":{"description":"Idempotency conflict, archived row, or email-account already linked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"429":{"description":"Write-tier rate limit (10/min, 50/hr, 100/day) exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/automations/email":{"post":{"tags":["Automations"],"summary":"Create Email automation","description":"Create an outbound email automation. Different recipient model from outreach — use `selection_mode: mailing_list` (with vault list IDs or upload emails inline) OR `selection_mode: filters` (creator search criteria). Constraint: each `email_account_id` can be linked to at most ONE active email automation.","operationId":"automation_create_email","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}},{"name":"X-Created-Via","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Created-Via"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationEmailRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationWriteResponseV2"}}}},"400":{"description":"Validation, missing/invalid Idempotency-Key, or forbidden update field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"403":{"description":"API key lacks `read_write` scope (`WRITE_NOT_PERMITTED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"404":{"description":"Automation/list/group not in this shop.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"409":{"description":"Idempotency conflict, archived row, or email-account already linked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"429":{"description":"Write-tier rate limit (10/min, 50/hr, 100/day) exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/automations/sample-request":{"post":{"tags":["Automations"],"summary":"Create Sample Request auto-processing automation","description":"Auto-approve OR auto-reject incoming TikTok Shop sample requests by criteria. Side-effect: a CRM Group is internally created from the `minimum_performance_criteria` so creator membership stays dynamic. The new `crm_group_id` is returned in `side_effects`. If CRM-group creation fails, the automation still creates (logged + Sentry breadcrumb). Sample request type uses `target_list` (vault list IDs) instead of `creators_to_include`.","operationId":"automation_create_sample_request","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}},{"name":"X-Created-Via","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Created-Via"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationSampleRequestRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationWriteResponseV2"}}}},"400":{"description":"Validation, missing/invalid Idempotency-Key, or forbidden update field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"403":{"description":"API key lacks `read_write` scope (`WRITE_NOT_PERMITTED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"404":{"description":"Automation/list/group not in this shop.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"409":{"description":"Idempotency conflict, archived row, or email-account already linked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"429":{"description":"Write-tier rate limit (10/min, 50/hr, 100/day) exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/automations/{automation_id}/start":{"post":{"tags":["Automations"],"summary":"Start automation","description":"Transition automation to running. Idempotent — already-running returns 200. Rejects with 422 `AUTOMATION_NOT_STARTABLE` when the automation can never run as configured (no schedule day with maxCreators > 0, or `valid_until`/`end_date` already passed) — this prevents the 'start succeeded but nothing ever sends' trap.","operationId":"automation_start","parameters":[{"name":"automation_id","in":"path","required":true,"schema":{"type":"integer","title":"Automation Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationWriteResponseV2"}}}},"400":{"description":"Validation, missing/invalid Idempotency-Key, or forbidden update field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"403":{"description":"API key lacks `read_write` scope (`WRITE_NOT_PERMITTED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"404":{"description":"Automation/list/group not in this shop.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"409":{"description":"Idempotency conflict, archived row, or email-account already linked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"429":{"description":"Write-tier rate limit (10/min, 50/hr, 100/day) exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"422":{"description":"Automation cannot run as configured — no schedule day with maxCreators > 0, or `valid_until`/`end_date` already passed (`AUTOMATION_NOT_STARTABLE`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}}}}},"/automations/{automation_id}/stop":{"post":{"tags":["Automations"],"summary":"Stop automation","description":"Transition automation to stopped. Idempotent.","operationId":"automation_stop","parameters":[{"name":"automation_id","in":"path","required":true,"schema":{"type":"integer","title":"Automation Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationWriteResponseV2"}}}},"400":{"description":"Validation, missing/invalid Idempotency-Key, or forbidden update field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"403":{"description":"API key lacks `read_write` scope (`WRITE_NOT_PERMITTED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"404":{"description":"Automation/list/group not in this shop.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"409":{"description":"Idempotency conflict, archived row, or email-account already linked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"429":{"description":"Write-tier rate limit (10/min, 50/hr, 100/day) exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/personalization/preview":{"post":{"tags":["Personalization"],"summary":"Preview the personalized message a single creator would receive","description":"Generate the AI personalization blocks for one creator and return the fully assembled message — exactly what a send with the same configuration would ship. When the creator has too little signal to personalize, the brand `fallback_message` is returned instead (`is_fallback: true`).\n\nContent Discussion and Product Discussion are AI-generated from the creator's own signals; Product Description / Selling Points / Offer are the fixed strings you pass in (resolve product blocks first via `POST /personalization/product-blocks`). Region and brand name are taken from the shop (brand name defaults to the shop name).\n\nAPI previews are stateless: nothing is stored, and a later send generates its own copy (or reuses a lock approved in the Reacher portal composer). Generation endpoints share a separate quota (20/min, 500/hr per key). Requires a single shop.","operationId":"personalization_preview","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PersonalizationPreviewRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PersonalizationPreviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/personalization/product-blocks":{"post":{"tags":["Personalization"],"summary":"Resolve product description + derive selling points","description":"Resolve the two product-sourced personalization blocks for one of your products: `[Product Description]` (the product's own catalog text) and `[Product Selling Points]` (2–3 concrete selling points DERIVED from the product's real title/description/attributes — never invented). Use the returned strings verbatim in `POST /personalization/preview` and in the `personalization` block of `POST /automations/dm`, so preview and send share the same fixed copy.\n\nBoth blocks degrade to empty strings (the block simply drops out of the message) ONLY when the product isn't in this shop's catalog or has too little grounded content; a failed lookup (transient database error) returns 500 instead, so empty strings are always a true statement about the product, never an outage artifact — safe to store. Requires a single shop.","operationId":"personalization_product_blocks","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PersonalizationProductBlocksRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PersonalizationProductBlocksResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/personalization/retone":{"post":{"tags":["Personalization"],"summary":"Rewrite a message body in a new tone","description":"Rewrite the shared message body in a new tone (`engaging` | `bold` | `inspirational`), preserving every `[Block]` token so the template stays intact. This is brand-level copy (one message for everyone) — there's no creator here. Degrades to the original body on any failure, so the caller never ends up with a broken template. Requires a single shop.","operationId":"personalization_retone","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PersonalizationRetoneRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PersonalizationRetoneResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/personalization/automations/{automation_id}/fallback-stats":{"get":{"tags":["Personalization"],"summary":"Personalization fallback usage for one automation","description":"How many of an automation's personalized outreach messages used the brand fallback (creator had too little signal to personalize) versus were fully personalized. A lifetime aggregate across every send run of the automation, plus the `fallback_rate` convenience ratio. Counts are recorded when the send engine locks the copy for delivery (immediately before the DM goes out), so a delivery that fails at the last step may still be counted. An automation with no personalized sends yet returns zeros; an automation not in this shop returns 404. Requires a single shop.","operationId":"personalization_fallback_stats","parameters":[{"name":"automation_id","in":"path","required":true,"schema":{"type":"integer","title":"Automation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PersonalizationFallbackStatsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/personalization/creators/{creator_id}/sent-messages":{"get":{"tags":["Personalization"],"summary":"Personalized messages a creator was actually sent","description":"The personalized outreach messages this shop sent to one creator (the creator record's audit trail), newest first — the exact copy, whether it was fully personalized or fell back. Rows are recorded when the send engine locks the copy for delivery, so `sent_at` marks the delivery attempt, not a provider receipt. `creator_id` is the TikTok creator id (as returned by `GET /automations/{id}/creators` or `GET /automations/{id}/sent-messages`). Returns an empty list when this creator has never been personalized in this shop. Requires a single shop.","operationId":"personalization_creator_sent_messages","parameters":[{"name":"creator_id","in":"path","required":true,"schema":{"type":"string","title":"Creator Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PersonalizationCreatorSentMessagesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/target-collabs":{"post":{"tags":["Target Collabs"],"summary":"Send a Target Collab invite directly","description":"Send a single TikTok Shop Target Collab (TC) invitation to a creator by handle. This is a convenience surface — it internally creates a single-creator TC automation and immediately starts it.\n\n**Queued semantics.** The response returns immediately with `status: \"queued\"`. Actual TC delivery happens asynchronously through Reacher's TC delivery worker (browser automation, the same path the portal uses). Typical end-to-end latency is a few seconds, sometimes longer under worker load. Poll `GET /public/v1/automations/{automation_id}` for delivery status and `target_collabs.invitation_id` once the worker submits.\n\n**Not for bulk.** For >10 invites at a time, use `POST /public/v1/automations/target-collab` directly with a multi-creator audience — that's more efficient than calling this endpoint in a loop, and exposes the full TC feature set (content type, sample policy, follow-up DMs).\n\n**Required.** `read_write` scope, `Idempotency-Key` header. Pass `X-Dry-Run: true` to validate without persisting or queueing.","operationId":"target_collab_direct_invite","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TargetCollabDirectRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TargetCollabDirectResponse"}}}},"400":{"description":"Validation, missing/invalid Idempotency-Key, or multi-shop scope (`x-shop-id=all`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"403":{"description":"API key lacks `read_write` scope (`WRITE_NOT_PERMITTED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"404":{"description":"creator_handle could not be resolved in this shop's region (`CREATOR_NOT_FOUND`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"409":{"description":"Idempotency conflict — same key, different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"429":{"description":"Write-tier rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/target-collabs/support-contact-default":{"get":{"tags":["Target Collabs"],"summary":"Get the shop's default Target Collab support contact","description":"Read the shop-level default `support_contact` used as a fallback when a TC create request omits `support_contact` (CORE-5428).\n\nReturns `configured: false` (with null fields) when no default is set — in that state TC creates still require a per-request `support_contact`. Requires a single shop (`x-shop-id`).","operationId":"target_collab_get_support_contact_default","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShopSupportContactDefaultResponse"}}}}}},"put":{"tags":["Target Collabs"],"summary":"Set the shop's default Target Collab support contact","description":"Configure the shop-level default `support_contact` once, so TC create requests can omit it (CORE-5428). A per-request `support_contact` still overrides this default for that request.\n\n`email` is required; `phone` is optional. The upsert is keyed on the shop, so re-sending the same body yields the same state. Requires `read_write` scope, a single shop (`x-shop-id`), and an `Idempotency-Key` header (for contract consistency with the other public-API write endpoints) — replay/conflict semantics match those writes.","operationId":"target_collab_set_support_contact_default","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShopSupportContactDefaultRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShopSupportContactDefaultResponse"}}}},"400":{"description":"Multi-shop scope (`x-shop-id=all` or CSV) — requires a single shop.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"403":{"description":"API key lacks `read_write` scope (`WRITE_NOT_PERMITTED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"409":{"description":"Idempotency conflict — same key, different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"429":{"description":"Write-tier rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"503":{"description":"Storage not yet available (migration 053 not applied).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/creator-messages/conversations":{"get":{"tags":["Creator Messages"],"summary":"List creator conversations","description":"The shop's creator DM inbox. Use `unreplied_only=true` to get conversations awaiting a reply (what an AI agent typically iterates).","operationId":"list_conversations_creator_messages_conversations_get","parameters":[{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"unread_only","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Unread Only"}},{"name":"unreplied_only","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Unreplied Only"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorConversationsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/creator-messages/conversations/{creator_handle}/messages":{"get":{"tags":["Creator Messages"],"summary":"Get the message thread with a creator","operationId":"get_conversation_messages_creator_messages_conversations__creator_handle__messages_get","parameters":[{"name":"creator_handle","in":"path","required":true,"schema":{"type":"string","title":"Creator Handle"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorMessagesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/creator-messages/conversations/{creator_handle}/reply":{"post":{"tags":["Creator Messages"],"summary":"Send a text reply to a creator","description":"Sends a plain-text DM to the creator in an EXISTING conversation (the creator must have an open thread with the shop — TikTok does not allow cold-opening a DM). Idempotency-Key required. X-Dry-Run: true validates and echoes without contacting TikTok.","operationId":"reply_to_creator_creator_messages_conversations__creator_handle__reply_post","parameters":[{"name":"creator_handle","in":"path","required":true,"schema":{"type":"string","title":"Creator Handle"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorReplyRequest"}}}},"responses":{"200":{"description":"Dry-run — validated and echoed, nothing sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorReplyDryRunResponse"}}}},"201":{"description":"Reply sent to the creator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorReplyResponse"}}}},"400":{"description":"Invalid request."},"403":{"description":"API key lacks `read_write` scope (`WRITE_NOT_PERMITTED`)."},"404":{"description":"No existing conversation with this creator for the shop."},"429":{"description":"Write rate limit exceeded."},"502":{"description":"TikTok rejected or failed the send."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/creator-messages/conversations/{creator_handle}/reply/image":{"post":{"tags":["Creator Messages"],"summary":"Send an image reply to a creator","description":"Send a TikTok IMAGE message. Provide an HTTPS `image_url`; we fetch it server-side, upload to TikTok, and dispatch into the existing conversation. Same auth/scope/idempotency/rate-limit/X-Dry-Run semantics as the text reply. v1: no caption (send a text reply separately if you also want copy).","operationId":"reply_to_creator_image_creator_messages_conversations__creator_handle__reply_image_post","parameters":[{"name":"creator_handle","in":"path","required":true,"schema":{"type":"string","title":"Creator Handle"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorReplyImageRequest"}}}},"responses":{"200":{"description":"Dry-run — validated and echoed, nothing fetched or sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorReplyImageDryRunResponse"}}}},"201":{"description":"Image uploaded to TikTok and reply sent to the creator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorReplyImageResponse"}}}},"400":{"description":"Invalid image_url (scheme, IP, content-type, or size)."},"403":{"description":"API key lacks `read_write` scope."},"404":{"description":"No existing conversation with this creator."},"429":{"description":"Write rate limit exceeded."},"502":{"description":"Image fetch failed, or TikTok rejected the send."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/creator-messages/conversations/{creator_handle}/reply/product":{"post":{"tags":["Creator Messages"],"summary":"Send a product-card reply to a creator","description":"Send a TikTok PRODUCT_CARD message linking one of THIS shop's products. We verify the `product_id` belongs to the shop's catalog. Same auth/scope/idempotency/rate-limit/X-Dry-Run semantics as the text reply. No caption (send a text reply separately for copy).","operationId":"reply_to_creator_product_creator_messages_conversations__creator_handle__reply_product_post","parameters":[{"name":"creator_handle","in":"path","required":true,"schema":{"type":"string","title":"Creator Handle"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorReplyProductRequest"}}}},"responses":{"200":{"description":"Dry-run — validated and echoed, nothing sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorReplyProductDryRunResponse"}}}},"201":{"description":"Product card sent to the creator.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorReplyProductResponse"}}}},"400":{"description":"Invalid request, or product_id not in this shop's catalog."},"403":{"description":"API key lacks `read_write` scope."},"404":{"description":"No existing conversation with this creator."},"429":{"description":"Write rate limit exceeded."},"502":{"description":"TikTok rejected or failed the send."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/creator-messages/draft":{"post":{"tags":["Creator Messages"],"summary":"Stage a draft reply on a creator conversation","description":"Persist a reply on the conversation WITHOUT delivering it to the creator. The draft surfaces in the Reacher inbox for a human to review and either send via the existing /reply* endpoints or discard via DELETE /drafts/{id}. Idempotency-Key required. X-Dry-Run: true validates and echoes without persisting.","operationId":"create_draft_creator_messages_draft_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDraftRequest"}}}},"responses":{"200":{"description":"Dry-run — validated and echoed, nothing persisted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDraftDryRunResponse"}}}},"201":{"description":"Draft staged on the conversation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDraftResponse"}}}},"400":{"description":"Invalid body (multiple content fields set, none set, etc.)."},"403":{"description":"API key lacks `read_write` scope."},"404":{"description":"No existing conversation with this creator for the shop."},"429":{"description":"Write rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/creator-messages/conversations/{creator_handle}/drafts":{"get":{"tags":["Creator Messages"],"summary":"List staged drafts on a conversation","description":"Drafts for the given creator on this shop, newest first. Drafts only — does NOT return sent messages (see GET /conversations/{handle}/messages for those).","operationId":"list_drafts_creator_messages_conversations__creator_handle__drafts_get","parameters":[{"name":"creator_handle","in":"path","required":true,"schema":{"type":"string","title":"Creator Handle"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":25,"title":"Page Size"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/creator-messages/drafts/{draft_id}":{"delete":{"tags":["Creator Messages"],"summary":"Discard a staged draft","description":"Remove a draft by id. 404 if the id is unknown, 403 if the draft belongs to a different shop than the caller's x-shop-id. Idempotency-Key is NOT required for delete (delete is naturally idempotent — a second DELETE just returns 404).","operationId":"delete_draft_endpoint_creator_messages_drafts__draft_id__delete","parameters":[{"name":"draft_id","in":"path","required":true,"schema":{"type":"integer","title":"Draft Id"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"responses":{"204":{"description":"Draft removed."},"200":{"description":"Dry-run — validated and echoed, nothing deleted."},"403":{"description":"API key lacks `read_write` scope, or draft belongs to a different shop."},"404":{"description":"Draft not found."},"429":{"description":"Write rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/campaigns":{"get":{"tags":["Campaigns"],"summary":"List campaigns","description":"Paginated list of the shop's creator campaigns with status/type filters and sortable columns. Returns counts per status tab (`all/active/past/draft`) and per type (retainer/challenge/leaderboard).","operationId":"list_campaigns_campaigns_get","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Page Size"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"UI alias: 'active' (= published), 'past' (= ended), 'draft' (= unpublished drafts), or 'archived'.","title":"Status"},"description":"UI alias: 'active' (= published), 'past' (= ended), 'draft' (= unpublished drafts), or 'archived'."},{"name":"campaign_type","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/CampaignType"},{"type":"null"}],"description":"Filter: retainer | challenge | leaderboard | live_gmv_challenge.","title":"Campaign Type"},"description":"Filter: retainer | challenge | leaderboard | live_gmv_challenge."},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Search"}},{"name":"date_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD; start_date >= date_from","title":"Date From"},"description":"YYYY-MM-DD; start_date >= date_from"},{"name":"date_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"YYYY-MM-DD; end_date <= date_to","title":"Date To"},"description":"YYYY-MM-DD; end_date <= date_to"},{"name":"sort_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"campaign_name | campaign_type | gmv | start_date | end_date | total_posts | total_views | created_at","title":"Sort By"},"description":"campaign_name | campaign_type | gmv | start_date | end_date | total_posts | total_views | created_at"},{"name":"sort_order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","default":"desc","title":"Sort Order"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Campaigns"],"summary":"Create a campaign","description":"Create a Retainer / Challenge / Leaderboard / Live GMV Challenge campaign. `X-Dry-Run: true` validates the body and echoes what would be submitted without writing any rows. Image fields take HTTPS URL strings — host the image yourself and pass the URL.\n\nThe 201 response includes an `invite_link` field — the public URL the caller can share with creators to bring them into the campaign. Subdomain is picked by `creator_source`: legacy `social_army` campaigns use `https://soco.reacherapp.com`; everything else (`my_community`, etc.) uses `https://creator.reacherapp.com`. Full pattern: `{base}/campaigns/{shop_id}/{uuid_id}`. Every campaign endpoint that returns a campaign payload carries the same field — POST, GET list, GET single, GET detail — so callers can lift it directly without constructing the URL themselves.","operationId":"create_campaign_campaigns_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignCreatedResponse"}}}},"200":{"description":"Dry-run echo (no campaign created)."},"400":{"description":"Invalid request."},"403":{"description":"API key lacks `read_write` scope."},"409":{"description":"Idempotency-Key conflict."},"429":{"description":"Write rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/campaigns/creators":{"get":{"tags":["Campaigns"],"summary":"List managed creators across all campaigns","description":"All creators currently linked to the shop's campaigns. Includes overview metrics (total creators, multi-campaign creators, overall Return-On-Campaign-Spend) plus a paginated per-creator list.","operationId":"list_managed_creators_campaigns_creators_get","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Page Size"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Search"}},{"name":"sort_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sort By"}},{"name":"sort_order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","default":"desc","title":"Sort Order"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/campaigns/content":{"get":{"tags":["Campaigns"],"summary":"List shop-wide creator content","description":"All videos posted by creators across the shop's campaigns. Returns overview metrics (total posts, views, orders, GMV, wallet balance) plus a paginated content list. Use the campaign-detail endpoint for a campaign-scoped view.","operationId":"list_content_campaigns_content_get","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Page Size"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Search"}},{"name":"sort_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sort By"}},{"name":"sort_order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","default":"desc","title":"Sort Order"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/campaigns/payments/wallet-balance":{"get":{"tags":["Campaigns"],"summary":"Get marketplace wallet balance","operationId":"get_wallet_balance_campaigns_payments_wallet_balance_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/campaigns/payments/history":{"get":{"tags":["Campaigns"],"summary":"Payment / settlement history","operationId":"get_payment_history_campaigns_payments_history_get","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Page Size"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Search"}},{"name":"sort_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sort By"}},{"name":"sort_order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","default":"desc","title":"Sort Order"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/campaigns/payments/deposits":{"get":{"tags":["Campaigns"],"summary":"Wallet deposit history","operationId":"get_deposit_history_campaigns_payments_deposits_get","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Page Size"}},{"name":"sort_by","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sort By"}},{"name":"sort_order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","default":"desc","title":"Sort Order"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/campaigns/{campaign_id}":{"get":{"tags":["Campaigns"],"summary":"Get one campaign (edit-view shape)","description":"Returns the campaign in the same shape `PATCH /campaigns/{id}` accepts.","operationId":"get_campaign_campaigns__campaign_id__get","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Campaign Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["Campaigns"],"summary":"Update an existing campaign","description":"Modify campaign fields. `campaign_type` and `creator_source` are immutable post-create — pass them and you'll get 400 IMMUTABLE_FIELD.","operationId":"update_campaign_campaigns__campaign_id__patch","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Campaign Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignUpdatedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Campaigns"],"summary":"Soft-delete a campaign","description":"Sets `status='deleted'`. The row remains in the DB but is hidden from list/detail views. Idempotent.","operationId":"delete_campaign_campaigns__campaign_id__delete","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Campaign Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignDeletedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/campaigns/{campaign_id}/detail":{"get":{"tags":["Campaigns"],"summary":"Get the full campaign detail view","description":"Full campaign detail: campaign metadata, applied/active creators with stage + payment-due + per-creator actions, aggregated stats (GMV, views, orders, payouts), and a content snippet. Equivalent to what the portal's campaign-detail page renders.","operationId":"get_campaign_detail_campaigns__campaign_id__detail_get","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Campaign Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/campaigns/{campaign_id}/creators/{creator_id}/accept":{"post":{"tags":["Campaigns"],"summary":"Accept a creator into a campaign","description":"Sets the creator's `CampaignCreatorMapping.status='active'`. Sends an acceptance email + SMS to the creator (fire-and-forget) — these are SKIPPED on `X-Dry-Run: true`.","operationId":"accept_creator_campaigns__campaign_id__creators__creator_id__accept_post","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Campaign Id"}},{"name":"creator_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Creator Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/AcceptCreatorRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorStateChangeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/campaigns/{campaign_id}/creators/{creator_id}/reject":{"post":{"tags":["Campaigns"],"summary":"Reject a creator from a campaign","description":"Sets `CampaignCreatorMapping.status='rejected'`. Sends a rejection email + SMS to the creator (fire-and-forget) — SKIPPED on dry-run.","operationId":"reject_creator_campaigns__campaign_id__creators__creator_id__reject_post","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Campaign Id"}},{"name":"creator_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Creator Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/RejectCreatorRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorStateChangeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/campaigns/{campaign_id}/creators/{creator_id}/reactivate":{"post":{"tags":["Campaigns"],"summary":"Reactivate a previously-rejected creator","description":"Reverses a reject: status goes back to `active`. No new notification is sent.","operationId":"reactivate_creator_campaigns__campaign_id__creators__creator_id__reactivate_post","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Campaign Id"}},{"name":"creator_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Creator Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorStateChangeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/campaigns/creators/{creator_id}/remove":{"post":{"tags":["Campaigns"],"summary":"Remove a creator from a campaign","description":"Removes the creator from ONE campaign (terminal `removed` status). The campaign lives in the `x-shop-id` shop; pass its id in the body. Removed creators disappear from the creator's portal views.\n\n**Paid/completed creators are blocked (409)** — their payout ledger is preserved and never overwritten.\n\n**Idempotent:** removing an already-`removed` creator returns 200 with `code='already_removed'` (no error). Combined with the `Idempotency-Key` replay, repeated calls are always safe.\n\n`X-Dry-Run: true` validates + echoes the intended action without writing or sending the optional SMS.","operationId":"remove_creator_campaigns_creators__creator_id__remove_post","parameters":[{"name":"creator_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Creator Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/RemoveCreatorRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Removed (or idempotent already-removed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveCreatorResponse"}}}},"400":{"description":"Invalid request / missing single shop."},"403":{"description":"API key lacks `read_write` scope."},"404":{"description":"Mapping/campaign not in this shop, or shop not owned by the key's customer."},"409":{"description":"Paid/completed creator (ledger preserved), or Idempotency-Key conflict."},"429":{"description":"Write rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/campaigns/creators/{creator_id}/add-to-campaign":{"post":{"tags":["Campaigns"],"summary":"Add a creator to another campaign (move)","description":"Adds the creator to a target campaign — purely additive (never removes them from the source campaign). The SOURCE shop is your `x-shop-id`; the TARGET shop + campaign are in the body.\n\n**Restrictions:**\n- **Region** — the target shop must be region-compatible with the source shop. GLOBAL and US are interchangeable; every other region must match exactly. Mismatch → 409.\n- **Same customer** — the target shop must belong to your API key's customer (cross-customer target → opaque 404).\n- **No double-add** — if the creator is already ACTIVE (or paid/completed) in the target → 409. A passive/terminal-inactive mapping (viewed/pending/invited/applied/removed/rejected/archived/withdrawn) is UPGRADED to active with NO duplicate row (`pg_advisory_xact_lock` guards it; a `paid_at` row is never overwritten).\n\nRewards are recomputed server-side from the TARGET campaign. `notify` (default true) sends the creator an acceptance-style notification — SKIPPED on `X-Dry-Run`.","operationId":"add_creator_to_campaign_campaigns_creators__creator_id__add_to_campaign_post","parameters":[{"name":"creator_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Creator Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddToCampaignRequest"}}}},"responses":{"200":{"description":"Added or upgraded to active.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddToCampaignResponse"}}}},"400":{"description":"Invalid request / missing single shop / target campaign not available."},"403":{"description":"API key lacks `read_write` scope, or creator not on the source shop's roster."},"404":{"description":"Target shop/campaign not found, or not owned by the key's customer."},"409":{"description":"Region mismatch, already-active in target, or Idempotency-Key conflict."},"429":{"description":"Write rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/campaigns/content/view":{"post":{"tags":["Campaigns"],"summary":"Get a single video's full detail by id","operationId":"view_video_content_campaigns_content_view_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewVideoContentRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoContentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/campaigns/content/tracker-vids":{"post":{"tags":["Campaigns"],"summary":"List a creator-campaign mapping's posted videos","description":"Used by the settlement screen to enumerate the videos a creator posted under a specific campaign.","operationId":"get_tracker_videos_campaigns_content_tracker_vids_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrackerVideosRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrackerVideosResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/campaigns/content/archive":{"post":{"tags":["Campaigns"],"summary":"Archive a creator's content tracker","description":"Soft-deletes a CampaignCreatorMapping's content tracker so it no longer surfaces in campaign content lists.","operationId":"archive_tracker_campaigns_content_archive_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArchiveTrackerRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArchiveTrackerResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/campaigns/content/request-more":{"post":{"tags":["Campaigns"],"summary":"Request more content from a creator","description":"Updates the creator's posts requirement and minimum GMV target on their `CampaignCreatorMapping`, then fires a notification (email + SMS) asking for additional posts — notifications SKIPPED on dry-run.","operationId":"request_more_content_campaigns_content_request_more_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestMoreContentRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestMoreContentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/campaigns/payments/deposit-intent":{"post":{"tags":["Campaigns"],"summary":"Create a Stripe Checkout session to top up the marketplace wallet","description":"Returns a Stripe Checkout `session_id` + `redirect_url`. Creating the intent does NOT move money — money moves only when the user completes the hosted checkout. `X-Dry-Run: true` validates the body without calling Stripe.","operationId":"create_deposit_intent_campaigns_payments_deposit_intent_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDepositIntentRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DepositIntentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/campaigns/payments/settle":{"post":{"tags":["Campaigns"],"summary":"Settle a creator payment","description":"Triggers a real Stripe transfer + wallet debit when `reward_type='cash'`. `reward_type='custom'` records the settlement only — no money moves. Idempotency-Key is REQUIRED and the inner `PaymentService.settle` runs claim/release semantics so a retry with the same key can never double-pay. Every successful settlement emits an `audit.campaigns.settle` log line for on-call reconciliation. `X-Dry-Run: true` skips the Stripe call entirely.","operationId":"settle_payment_campaigns_payments_settle_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SettlePaymentRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SettlementResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/campaigns/{campaign_id}/archive":{"post":{"tags":["Campaigns"],"summary":"Archive a campaign","description":"Sets `status='archived'` — preserves the row in list/detail views but marks it as no longer active.","operationId":"archive_campaign_campaigns__campaign_id__archive_post","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Campaign Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignArchivedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/lists":{"post":{"tags":["Lists"],"summary":"Create a list","description":"Upload a list of creator handles + emails. Two paths: JSON body OR multipart CSV (file field name `file`, columns `creator_name` required + `email` optional). Returned `list_id` is what callers pass to automations as `lists_selected`.","operationId":"list_create","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListResponse"}}}},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}},"description":"Bad Request"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}},"description":"Forbidden"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}},"description":"Not Found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}},"description":"Conflict"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}},"description":"Too Many Requests"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"additionalProperties":false,"description":"JSON-body create. The multipart CSV path is at the route layer\n(`POST /public/v1/lists` with `multipart/form-data` and a `file` field).","properties":{"name":{"description":"Human-readable list name shown in the portal.","maxLength":120,"minLength":1,"title":"Name","type":"string"},"description":{"anyOf":[{"maxLength":500,"type":"string"},{"type":"null"}],"title":"Description"},"creator_handles":{"description":"TikTok handles, with or without leading '@'. Deduplicated case-insensitively at insert time.","items":{"type":"string"},"maxItems":50000,"title":"Creator Handles","type":"array"},"creator_emails":{"description":"Email addresses. Deduplicated case-insensitively at insert time.","items":{"type":"string"},"maxItems":50000,"title":"Creator Emails","type":"array"}},"required":["name"],"title":"ListCreateRequest","type":"object"}},"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"CSV with `creator_name` (required) + optional `email` columns."},"name":{"type":"string","description":"Human-readable list name."},"description":{"type":"string","nullable":true}},"required":["file","name"]}}}}},"get":{"tags":["Lists"],"summary":"List the shop's lists","operationId":"list_lists_endpoint_lists_get","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Page Size"}},{"name":"name_contains","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name Contains"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListsPaginatedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/lists/{list_id}":{"get":{"tags":["Lists"],"summary":"Get a list with its members","operationId":"list_get_endpoint_lists__list_id__get","parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"integer","title":"List Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListMembersResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Lists"],"summary":"Delete a list (soft)","description":"Sets state=archived. Idempotent — already-archived returns 200.","operationId":"list_delete","parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"integer","title":"List Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}},"description":"Bad Request"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}},"description":"Forbidden"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}},"description":"Not Found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}},"description":"Conflict"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}},"description":"Too Many Requests"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/crm-groups":{"get":{"tags":["CRM Groups"],"summary":"List CRM Groups","description":"Paginated list of CRM Groups for this shop — creator cohorts you can target or exclude in automations. Membership is live for filter-built groups and frozen for groups built from a dashboard segment; see `conditions` on each group. Create segment-based groups with POST /crm-groups/from-segment; filter-built groups are still portal-only.","operationId":"crm_group_list","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Page Size"}},{"name":"name_contains","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name Contains"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrmGroupsPaginatedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/crm-groups/from-segment":{"post":{"tags":["CRM Groups"],"summary":"Create a CRM Group from a dashboard segment","description":"Build a group from a Creators-tab segment — a GMV `tier`, an `automation_id`, and a date window — rather than from raw filter conditions. Use it to turn 'creators in tier L3 reached by automation 812 last month' into a reusable cohort you can target with an automation.\n\n**Membership is a snapshot.** Members are materialized once at creation and do not update as creators enter or leave the segment. Re-post to refresh.\n\n**The window is always bounded.** Omit `start_date`/`end_date` and the segment resolves over the trailing 28 days rather than the shop's whole history; the maximum span is 365 days. Pass both dates explicitly whenever the segment you have in mind is a specific period.\n\n**Creation upserts by name.** Reusing an existing `group_name` in this shop overwrites that group's definition and replaces its members.\n\n**Expect fewer creators than the dashboard shows.** Only CRM-tracked creators can be grouped, so DM-only and organic creators in the segment resolve to nothing. `handles_resolved` is what the segment matched; `creator_count` is what made it into the group. The gap between them is that drop-off — check it before treating the group as the segment.\n\n`X-Dry-Run: true` reports the segment size and whether it exceeds the group limit, without writing. It cannot show the CRM drop-off: that is only known once the group is built.","operationId":"crm_group_create_from_segment","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrmGroupFromSegmentRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrmGroupFromSegmentResponse"}}}},"200":{"description":"Dry-run result (X-Dry-Run: true). Nothing was written.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrmGroupFromSegmentResponse"}}}},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}},"description":"Bad Request"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}},"description":"Forbidden"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}},"description":"Not Found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}},"description":"Conflict"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}},"description":"Too Many Requests"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WriteErrorResponse"}}},"description":"Internal Server Error"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/crm-groups/{crm_group_id}":{"get":{"tags":["CRM Groups"],"summary":"Get a CRM Group's filter conditions + member count","operationId":"crm_group_get","parameters":[{"name":"crm_group_id","in":"path","required":true,"schema":{"type":"string","title":"Crm Group Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrmGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/metrics/timeseries":{"post":{"tags":["Metrics"],"summary":"Time-series metrics","description":"Fetch time-series data for one or more metrics over a date range. Returns daily/weekly/monthly data points for charting trends. Available metrics: gmv (affiliate-attributed), total_gmv (full shop from the Seller Center scrape), creators, videos_posted, video_views, gmv_per_video, gmv_per_sample, creators_reached, creators_messaged, tc_invites_sent, samples_approved, sample_requests, gmv_driving_videos, new_creators_posting, open_collabs, accepted_tc_count, emails_sent, dm_responses, reply_rate. Seller Center whole-shop cards: orders, units_sold, aov, ctr, conversion_rate (aov/ctr/conversion_rate are ratios computed over the full window, not averages of daily ratios; 0 for shops not yet on the Seller Center sync). Granularity (day/week/month) is auto-detected based on the date range if not specified. IMPORTANT: 'creators' returns distinct creators PER DAY — summing daily values double-counts creators active on multiple days. For the headline distinct count over the full period, use POST /metrics/summary instead. Similarly, gmv_per_video and gmv_per_sample are daily ratios — use /metrics/summary for the period-level headline values. Dates default to the newest day with settled data for the shop (typically yesterday; up to 2 days back while a day finalizes).","operationId":"timeseries_metrics_timeseries_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeseriesRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeseriesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/metrics/summary":{"post":{"tags":["Metrics"],"summary":"Dashboard headline metrics","description":"Pre-computed headline scalar metrics for a date range, matching the Reacher dashboard's top-level KPIs exactly. Use this for summary cards and headline numbers. Includes derived metrics like active_creators (distinct count, not daily sum), gmv_per_video, and gmv_per_sample. Also includes Seller Center whole-shop cards: orders, units_sold, aov, ctr, conversion_rate (all-channel; aov/ctr/conversion_rate are ratios computed over the full window). Defaults to the last 30 days if no dates are specified. Dates default to the newest day with settled data for the shop (typically yesterday; up to 2 days back while a day finalizes).","operationId":"summary_metrics_summary_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/MetricsSummaryRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricsSummaryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/shop-gmv/timeseries":{"post":{"tags":["Shop GMV"],"summary":"Daily shop GMV time series (Seller Center, full shop view)","description":"Daily GMV for one shop from the TikTok Seller Center daily rollup, including channel breakdown (video → affiliate/seller, live → affiliate/seller, product_card → shop_tab/search) and traffic (product impressions/clicks). \n\n**Differs from POST /metrics/timeseries metric=gmv** — that endpoint reports affiliate-attributed GMV only (Reacher's view of creator-driven sales). This endpoint reports the **total** shop GMV including ads / Smart+ and organic. Use this when the customer wants numbers matching their TikTok Seller Center dashboard. \n\nSingle-shop only — set ``x-shop-id`` to a specific shop ID. Defaults to last 30 days ending yesterday. Max range 90 days (TikTok backfill depth). The newest available day is yesterday; today's row is not returned. Empty ``series`` is a normal 200 response — Seller Center coverage is still rolling out.","operationId":"shop_gmv_timeseries_shop_gmv_timeseries_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShopGmvTimeseriesRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShopGmvTimeseriesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/shop-gmv/summary":{"post":{"tags":["Shop GMV"],"summary":"Shop GMV window totals (Seller Center, full shop view)","description":"Window totals for one shop from the TikTok Seller Center daily rollup — SUMmed across the window with the same channel hierarchy as the timeseries response. \n\nSame coverage caveats as the timeseries endpoint. ``aov`` is recomputed as total GMV / total orders over the window (a window-correct value, not the average of daily AOVs). ``customers`` is the SUM of daily unique-customer counts — buyers who purchased on multiple days are double-counted; a true window-distinct count is not derivable from this rollup.","operationId":"shop_gmv_summary_shop_gmv_summary_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ShopGmvSummaryRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShopGmvSummaryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/funnel":{"get":{"tags":["Analytics"],"summary":"Affiliate funnel overview","description":"Affiliate funnel stage counts matching the dashboard's 'Affiliate Funnel' section. Shows creators at each stage: sample requested, approved, content posted, content unfulfilled, GMV generated, and top creators (>$10k GMV). Data is based on the last 90 days.","operationId":"funnel_overview_funnel_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/gmv-max/campaigns":{"get":{"tags":["GMV Max"],"summary":"List GMV Max campaigns","description":"List all GMV Max (TikTok Smart+) campaigns for the shop(s) addressed by `x-shop-id`. Returns cached campaign metadata last synced from TikTok by the portal — call the portal's sync endpoints to refresh. Supports multi-shop (`x-shop-id: all` or comma-separated).","operationId":"list_campaigns_gmv_max_campaigns_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GmvMaxCampaignsResponse"}}}}}}},"/gmv-max/campaigns/{campaign_id}/metrics":{"get":{"tags":["GMV Max"],"summary":"Daily GMV Max campaign metrics","description":"Daily performance metrics (spend, impressions, clicks, orders, gross_revenue, ROAS, CPC, CPM, CTR) for one campaign over a date range. Date range may not exceed 90 days (inclusive). Defaults to the last 30 days. Multi-shop is supported — when multiple shops are addressed via `x-shop-id` (a comma-separated list or `all`), the campaign's owning shop is resolved automatically and metrics are returned for that shop.","operationId":"campaign_metrics_gmv_max_campaigns__campaign_id__metrics_get","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":64,"title":"Campaign Id"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"}},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GmvMaxCampaignMetricsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/gmv-max/dashboard":{"get":{"tags":["GMV Max"],"summary":"GMV Max dashboard KPIs","description":"Aggregated GMV Max KPIs for a shop over a recent window, with previous-period comparison: spend, revenue, orders, impressions, clicks, ROAS — plus a per-day breakdown for charting. Single shop only (set ``x-shop-id`` to a specific shop ID). Mirrors the portal Dashboard exactly.","operationId":"dashboard_gmv_max_dashboard_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":90,"minimum":1,"description":"Window size in days (default 7).","default":7,"title":"Days"},"description":"Window size in days (default 7)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/gmv-max/campaigns/{campaign_id}":{"get":{"tags":["GMV Max"],"summary":"GMV Max campaign detail","description":"Detail for one campaign: name, status, ad type, budget, ROAS bid, last sync time, plus the campaign's daily metric rows (spend, impressions, clicks, orders, gross_revenue, CPC, CPM, CTR, ROAS). Single shop only — the caller must address the campaign's owning shop via ``x-shop-id``. For multi-shop callers, use the ``/campaigns`` list endpoint to discover which shop owns a campaign before requesting its detail.","operationId":"campaign_detail_gmv_max_campaigns__campaign_id__get","parameters":[{"name":"campaign_id","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":64,"title":"Campaign Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/gmv-max/creatives":{"get":{"tags":["GMV Max"],"summary":"GMV Max creatives for a campaign","description":"List the synced creatives (videos, spark codes, etc.) attached to a specific GMV Max campaign, with per-creative performance summaries. Single shop only.","operationId":"creatives_gmv_max_creatives_get","parameters":[{"name":"campaign_id","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":64,"description":"GMV Max campaign ID.","title":"Campaign Id"},"description":"GMV Max campaign ID."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/gmv-max/spark-codes":{"get":{"tags":["GMV Max"],"summary":"GMV Max spark code sync state","description":"Cached spark-code sync state for the shop: which codes are queued, in flight, succeeded, or failed against TikTok. Read-only — sync itself is JWT-only on the portal. Single shop only.","operationId":"spark_codes_gmv_max_spark_codes_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/gmv-max/templates":{"get":{"tags":["GMV Max"],"summary":"GMV Max automation quick-start templates","description":"Hard-coded set of quick-start automation templates the portal exposes (e.g. 'Boost top performers', 'Wind down low-ROAS'). Useful as a starting point when scaffolding an automation via the Phase-2 writes API. Not shop-scoped — same set for every caller.","operationId":"templates_gmv_max_templates_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/gmv-max/automations":{"get":{"tags":["GMV Max"],"summary":"List GMV Max automations","description":"List all GMV Max automations configured for the shop, with their current status, trigger frequency, boost window, and last-run summary. Single shop only — automations are per-shop.","operationId":"automations_gmv_max_automations_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["GMV Max"],"summary":"Create a GMV Max automation","description":"Create a new automation that watches the selected campaign(s) and fires creative boosts when its conditions match. Requires ``read_write`` scope. Idempotent via the ``Idempotency-Key`` header — replays of the same key + same body return the cached 201 instead of creating a duplicate row.\n\n``X-Dry-Run: true`` validates the body and echoes the would-be row without touching the DB. Single shop only — set ``x-shop-id`` to the owning shop.\n\nEvery ``set_up_creative_boost`` action requires a ``total_creative_boost_cost`` guardrail (lifetime spend cap) — missing one returns 422 ``TOTAL_BUDGET_CAP_REQUIRED``. Each campaign_id can only belong to one active automation at a time — conflicts return 422 ``CAMPAIGN_ALREADY_ASSIGNED``.","operationId":"create_automation_gmv_max_automations_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationCreateRequestPublic"}}}},"responses":{"201":{"description":"Automation created.","content":{"application/json":{"schema":{}}}},"200":{"description":"Dry-run echo (no automation created)."},"400":{"description":"Invalid request."},"403":{"description":"API key lacks `read_write` scope."},"409":{"description":"Idempotency-Key conflict."},"422":{"description":"Validation error (cap missing, campaign conflict, etc.)."},"429":{"description":"Write rate limit exceeded."}}}},"/gmv-max/automations/{automation_id}":{"get":{"tags":["GMV Max"],"summary":"GMV Max automation detail","description":"Detail for one automation: full configuration (conditions, actions, guardrails) plus its 10 most recent runs. Single shop only — automation_ids are scoped per shop.","operationId":"automation_detail_gmv_max_automations__automation_id__get","parameters":[{"name":"automation_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Automation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["GMV Max"],"summary":"Update a GMV Max automation","description":"Patch an existing automation. Only active, non-expired automations are editable — stopped, deleted, or past-end-date rows return 422 with ``AUTOMATION_NOT_EDITABLE`` or ``AUTOMATION_EXPIRED``.\n\nPATCH semantics — fields the caller does NOT include are left unchanged. Sending ``null`` for ``boost_start_date`` / ``boost_end_date`` clears the boost window (both dates must be sent together).\n\n``X-Dry-Run: true`` echoes the patch without writing. Idempotent via the ``Idempotency-Key`` header. Single shop only.","operationId":"update_automation_gmv_max_automations__automation_id__put","parameters":[{"name":"automation_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Automation Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutomationUpdateRequestPublic"}}}},"responses":{"200":{"description":"Automation updated (or dry-run echo).","content":{"application/json":{"schema":{}}}},"403":{"description":"API key lacks `read_write` scope."},"404":{"description":"Automation not found in this shop."},"409":{"description":"Idempotency-Key conflict."},"422":{"description":"Validation error / automation not editable."},"429":{"description":"Write rate limit exceeded."}}},"delete":{"tags":["GMV Max"],"summary":"Delete (soft) a GMV Max automation","description":"Soft-delete an automation (``status='deleted'``). The row remains in the DB but is excluded from list / detail views. Re-deleting an already-deleted automation is a no-op that still returns 200 (idempotent on the row itself).\n\nPending boost rows queued by this automation are cancelled in the background — best-effort, not awaited here. Single shop only.","operationId":"delete_automation_gmv_max_automations__automation_id__delete","parameters":[{"name":"automation_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Automation Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"responses":{"200":{"description":"Automation deleted (or already deleted).","content":{"application/json":{"schema":{}}}},"403":{"description":"API key lacks `read_write` scope."},"404":{"description":"Automation not found in this shop."},"409":{"description":"Idempotency-Key conflict."},"429":{"description":"Write rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/gmv-max/automations/{automation_id}/runs":{"get":{"tags":["GMV Max"],"summary":"GMV Max automation run history","description":"Full run history for one automation (most recent first), including trigger source, start/finish timestamps, success/failure status, and per-run summary metrics. Single shop only.","operationId":"automation_runs_gmv_max_automations__automation_id__runs_get","parameters":[{"name":"automation_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Automation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/gmv-max/automations/{automation_id}/activities":{"get":{"tags":["GMV Max"],"summary":"GMV Max automation activity log","description":"Paginated per-event activity log for one automation (campaign boost / cancel events the automation produced), plus an aggregate summary across the full history. Supports the same ``status`` and ``search`` filters the portal does. Single shop only.","operationId":"automation_activities_gmv_max_automations__automation_id__activities_get","parameters":[{"name":"automation_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Automation Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by activity status (matches portal).","title":"Status"},"description":"Filter by activity status (matches portal)."},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"description":"Free-text search across activity rows.","title":"Search"},"description":"Free-text search across activity rows."},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Page Size"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/gmv-max/automations/{automation_id}/stop":{"post":{"tags":["GMV Max"],"summary":"Stop a GMV Max automation","description":"Mark an automation ``status='stopped'`` (terminal — no resume). Already-stopped automations are returned unchanged (idempotent on the row itself, independent of the ``Idempotency-Key`` header).\n\nBoost cancellation against TikTok is best-effort and runs in a background thread on the portal side; this endpoint does not wait for it. Single shop only.","operationId":"stop_automation_gmv_max_automations__automation_id__stop_post","parameters":[{"name":"automation_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"title":"Automation Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"responses":{"200":{"description":"Automation stopped (or already stopped).","content":{"application/json":{"schema":{}}}},"403":{"description":"API key lacks `read_write` scope."},"404":{"description":"Automation not found in this shop."},"409":{"description":"Idempotency-Key conflict."},"429":{"description":"Write rate limit exceeded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/gmv-max/spark-codes/sync":{"post":{"tags":["GMV Max"],"summary":"Sync specific spark codes to TikTok","description":"Synchronously authorise the listed spark codes against the shop's TikTok Business ad account. Each code fires one TikTok ``/tt_video/authorize/`` call:\n\n* On success → code's ``sync_status`` flips to ``synced``.\n* On TikTok QPS / rate-limit → code is left ``pending`` so the offline CRJ retries it; the response counts this in ``queued``.\n* On hard error → ``failed`` with the error string.\n\nUp to 100 codes per request. Shops without an active TikTok Business connection return 422 ``TIKTOK_NOT_CONNECTED``. Single shop only.","operationId":"sync_spark_codes_gmv_max_spark_codes_sync_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SparkCodeSyncRequestPublic"}}}},"responses":{"200":{"description":"Sync attempted; see `results` for per-code outcome.","content":{"application/json":{"schema":{}}}},"403":{"description":"API key lacks `read_write` scope."},"409":{"description":"Idempotency-Key conflict."},"422":{"description":"Shop not connected to TikTok Business."},"429":{"description":"Write rate limit exceeded."}}}},"/gmv-max/spark-codes/sync-all":{"post":{"tags":["GMV Max"],"summary":"Queue every unsynced spark code for sync","description":"Queue every spark code for the shop that isn't already ``synced``. Writes pending rows to ``gmv_max_spark_code_sync``; the offline CRJ fires the TikTok ``/tt_video/authorize/`` calls on its next run.\n\nReturns immediately with ``{queued, skipped, message}``. Shops without an active TikTok Business connection return 422 ``TIKTOK_NOT_CONNECTED``. Single shop only.","operationId":"sync_all_spark_codes_gmv_max_spark_codes_sync_all_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"X-Dry-Run","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Dry-Run"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/SparkCodeSyncAllRequestPublic"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Codes queued for async sync.","content":{"application/json":{"schema":{}}}},"403":{"description":"API key lacks `read_write` scope."},"409":{"description":"Idempotency-Key conflict."},"422":{"description":"Shop not connected to TikTok Business."},"429":{"description":"Write rate limit exceeded."}}}},"/ai-search/creators":{"post":{"tags":["AI Search"],"summary":"Search creators with natural language","description":"Search the global TikTok creator universe with a natural-language query. Returns paginated creator results scored against the query, with optional browse mode when `query` is omitted (returns top creators in the shop's region). Requires a single `x-shop-id` — the shop's region determines which regional creator table is searched.","operationId":"search_creators_ai_search_creators_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AISearchCreatorsRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AISearchCreatorsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ai-search/export":{"post":{"tags":["AI Search"],"summary":"Export creator search results as CSV","description":"Export up to 50,000 matching creators as CSV. The response is a streaming attachment; the `X-Creator-Row-Count` response header reports the actual row count. CSV only — XLSX is not supported on the public API. Requires a single `x-shop-id`.","operationId":"export_creators_ai_search_export_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AISearchExportRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AISearchCreator":{"properties":{"creator_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Creator Name","description":"Creator's TikTok handle. Stable identifier for cross-referencing."},"categories":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Categories"},"follower_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Follower Count"},"gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gmv","description":"Estimated overall GMV (USD-normalized)."},"post_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Post Rate"},"engagement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Engagement Rate"},"bio":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bio","description":"Creator bio."},"top_videos":{"items":{"$ref":"#/components/schemas/AISearchVideo"},"type":"array","title":"Top Videos"}},"type":"object","title":"AISearchCreator"},"AISearchCreatorsRequest":{"properties":{"query":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Query","description":"Natural-language search query. Whitespace-only is treated as no query (browse mode)."},"page":{"type":"integer","minimum":1.0,"title":"Page","default":1},"page_size":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Page Size","default":20}},"additionalProperties":false,"type":"object","title":"AISearchCreatorsRequest","description":"POST /ai-search/creators request body."},"AISearchCreatorsResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/AISearchCreator"},"type":"array","title":"Data"},"pagination":{"$ref":"#/components/schemas/AISearchPagination"}},"type":"object","required":["data","pagination"],"title":"AISearchCreatorsResponse"},"AISearchExportRequest":{"properties":{"query":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Query","description":"Natural-language search query. Omit (or pass whitespace-only) to export the full browse universe for your shop's region."},"creator_ids":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":50000},{"type":"null"}],"title":"Creator Ids","description":"Explicit list of creator IDs to export. Mutually exclusive with query."}},"additionalProperties":false,"type":"object","title":"AISearchExportRequest","description":"POST /ai-search/export request body. CSV only, hard 50,000-row cap."},"AISearchPagination":{"properties":{"total":{"type":"integer","title":"Total"},"page":{"type":"integer","title":"Page"},"page_size":{"type":"integer","title":"Page Size"}},"type":"object","required":["total","page","page_size"],"title":"AISearchPagination"},"AISearchVideo":{"properties":{"video_id":{"type":"string","title":"Video Id"},"tiktok_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tiktok Url"},"views":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Views"}},"type":"object","required":["video_id"],"title":"AISearchVideo"},"AcceptCreatorRequest":{"properties":{"message":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Message","description":"Optional custom message included in the creator's acceptance email/SMS. If omitted, the campaign's default acceptance template is used."}},"additionalProperties":false,"type":"object","title":"AcceptCreatorRequest","description":"``POST /public/v1/campaigns/{campaign_id}/creators/{creator_id}/accept``.\n\nBody is optional — only used to override the default acceptance\nnotification message sent to the creator (SMS + email)."},"ActionRulePublic":{"properties":{"action_type":{"$ref":"#/components/schemas/ActionType","description":"What the action does. Currently only `set_up_creative_boost` is wired up to the execution engine."},"params":{"additionalProperties":true,"type":"object","title":"Params","description":"Action-specific parameters. For `set_up_creative_boost`: `{\"maxCapPerDay\": <float>}` is the per-day spend cap (mirrors the portal Create-Automation form)."}},"additionalProperties":false,"type":"object","required":["action_type"],"title":"ActionRulePublic","description":"One side-effect the automation performs when its conditions fire."},"ActionType":{"type":"string","enum":["set_up_creative_boost"],"title":"ActionType","description":"Side effect the automation performs when its conditions fire.\n\nOnly ``set_up_creative_boost`` is wired up to the offline execution\nengine today. The other portal-side enum values (``budget_adjust``,\n``material_status_update``, etc.) are template-preview-only and will\nnot actually execute; they are intentionally not exposed here so\ncallers don't silently create no-op automations."},"AddToCampaignRequest":{"properties":{"target_campaign_id":{"type":"integer","exclusiveMinimum":0.0,"title":"Target Campaign Id","description":"Campaign the creator is being added to (lives in target_shop_id)."},"target_shop_id":{"type":"integer","exclusiveMinimum":0.0,"title":"Target Shop Id","description":"Shop that owns the target campaign. Must belong to the same customer as your API key (else 404)."},"notify":{"type":"boolean","title":"Notify","description":"Send the creator an acceptance-style notification on add. SKIPPED on X-Dry-Run.","default":true}},"additionalProperties":false,"type":"object","required":["target_campaign_id","target_shop_id"],"title":"AddToCampaignRequest","description":"``POST /public/v1/campaigns/creators/{creator_id}/add-to-campaign``.\n\nAdds (or \"moves\" — additive, never removes from source) the creator\ninto a target campaign. The SOURCE shop is the request's ``x-shop-id``;\nthe TARGET shop + campaign are in the body. The target shop must:\n  * belong to the SAME customer as the API key (enforced at the route\n    layer; cross-customer target → opaque 404), and\n  * be region-compatible with the source shop — GLOBAL/US interchange-\n    able, every other region exact (enforced in the service → 409).\nAlready-active-in-target → 409; passive/terminal-inactive mappings\nupgrade to active (no duplicate row).\n\nNOTE: ``source_shop_id`` is intentionally NOT a body field — it is\nalways the request's ``x-shop-id`` (the shop the API key is acting on\nand that middleware has already ownership-validated). This avoids the\ninternal surface's \"query shop_id must match source_shop_id\" footgun."},"AddToCampaignResponse":{"properties":{"success":{"type":"boolean","title":"Success","default":true},"code":{"type":"string","title":"Code","default":"ok"},"data":{"additionalProperties":true,"type":"object","title":"Data","description":"`{campaign_creator_mapping_id, campaign_id, status='active'}`."}},"type":"object","title":"AddToCampaignResponse","description":"200 response for\n``POST /campaigns/creators/{creator_id}/add-to-campaign``.\n\n``data`` carries ``campaign_creator_mapping_id``, ``campaign_id`` (the\ntarget), and ``status`` (always ``\"active\"`` — a passive/terminal\nmapping that was upgraded reports ``active`` too, with NO duplicate\nrow created)."},"ArchiveTrackerRequest":{"properties":{"campaign_creator_mapping_id":{"type":"integer","exclusiveMinimum":0.0,"title":"Campaign Creator Mapping Id"}},"additionalProperties":false,"type":"object","required":["campaign_creator_mapping_id"],"title":"ArchiveTrackerRequest","description":"``POST /public/v1/campaigns/content/archive`` — soft-delete a\ncreator's content tracker so the campaign no longer surfaces it."},"ArchiveTrackerResponse":{"properties":{"status":{"type":"string","title":"Status"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"}},"type":"object","required":["status"],"title":"ArchiveTrackerResponse","description":"200 response for ``POST /campaigns/content/archive``."},"AutomationCreateRequestPublic":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name"},"template_type":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Template Type","description":"Optional reference to one of the `/templates` `template_type` values the automation was scaffolded from. Free-form — does not affect execution."},"trigger_frequency":{"$ref":"#/components/schemas/TriggerFrequency","description":"How often the automation evaluates. Only `daily` is wired up today.","default":"daily"},"campaign_ids":{"items":{"type":"string"},"type":"array","title":"Campaign Ids","description":"TikTok GMV Max campaign IDs the automation acts on. Each campaign_id can only be assigned to one active automation at a time — the controller rejects conflicts with a 422."},"conditions":{"items":{"$ref":"#/components/schemas/ConditionRulePublic"},"type":"array","title":"Conditions"},"actions":{"items":{"$ref":"#/components/schemas/ActionRulePublic"},"type":"array","title":"Actions"},"guardrails":{"items":{"$ref":"#/components/schemas/GuardrailRulePublic"},"type":"array","title":"Guardrails"},"boost_start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Boost Start Date","description":"First day boosts may fire. Must be paired with `boost_end_date`. Window cannot exceed 90 days."},"boost_end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Boost End Date"}},"additionalProperties":false,"type":"object","required":["name"],"title":"AutomationCreateRequestPublic","description":"``POST /public/v1/gmv-max/automations`` — create a GMV Max automation.\n\nMirrors ``modules.gmv_max.GmvMaxModels.AutomationCreateRequest`` minus\n``shop_id`` (injected from ``x-shop-id``)."},"AutomationCreatorOutcome":{"properties":{"creator_handle":{"type":"string","title":"Creator Handle","description":"TikTok handle as submitted."},"outcome":{"type":"string","title":"Outcome","description":"Machine-stable outcome: `invite_submitted` (TC invite queued to the worker), `outreach_sent` (DM submitted), `previously_invited` (already invited for this product), `invite_removed` (invitation was withdrawn/edited), `skipped` (worker evaluated but did not send — see `skip_reason`), or `unknown`."},"status":{"type":"string","title":"Status","description":"Human-readable status label, e.g. 'Outreach Sent'."},"detail":{"type":"string","title":"Detail","description":"Human-readable explanation of the outcome/status."},"skip_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Skip Reason","description":"Machine skip-reason code, present only when `outcome='skipped'`. One of the `CreatorSkipReason` values: `creator_not_found`, `creator_ineligible`, `five_message_limit_hit`, `previously_invited`, `previously_messaged`, `blocked_by_outreach_limit`, `performance_criteria_not_met`, `already_accepted_invite`, `conflicting_invitation`, `agency_sample_request_blocked`, `sample_request_expired`, `unknown`. Branch on this rather than parsing `detail`."},"invitation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invitation Id","description":"TikTok `target_collaboration_id` for this creator's Target Collab invite — the id used to reference or look up the collaboration. Populated only for creators invited via Target Collab (outcomes `invite_submitted` / `previously_invited` / `invite_removed`); `null` for DM outreach, skipped creators, and TC invites the worker has not submitted to TikTok yet (it is written asynchronously after the invite lands). This is an identifier, not a link: TikTok's TC-details API returns no shareable invite URL, so there is no ready-made link to hand to the creator."}},"type":"object","required":["creator_handle","outcome","status","detail"],"title":"AutomationCreatorOutcome","description":"One creator's outcome under an automation, assembled from the\nMessages / target_collabs / skipped_creators tables the worker writes.\n\nIMPORTANT — queued vs delivered: an ``invite_submitted`` /\n``outreach_sent`` outcome means Reacher's worker submitted the invite /\nDM through the same browser-automation path the portal uses. TikTok does\nNOT return a per-creator inbox-delivery receipt, so this is the most\nauthoritative delivery signal available — it is *not* a confirmed\n\"landed in the creator's TikTok inbox\" event."},"AutomationCreatorsResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/AutomationCreatorOutcome"},"type":"array","title":"Data"},"summary":{"$ref":"#/components/schemas/AutomationCreatorsSummary"},"pagination":{"title":"Pagination"}},"type":"object","required":["data","summary","pagination"],"title":"AutomationCreatorsResponse","description":"Per-creator outcome ledger for one automation. Read-only, single-shop.\n\nBacked by the always-present `skipped_creators` / `target_collabs` /\n`Messages` tables — so unlike the aggregate `creators_reached` / `skipped`\ncounters on the list/detail responses (which read null once the stats\nview ages out an idle automation), this endpoint stays populated for the\nlife of the automation."},"AutomationCreatorsSummary":{"properties":{"total":{"type":"integer","title":"Total","default":0},"invite_submitted":{"type":"integer","title":"Invite Submitted","default":0},"outreach_sent":{"type":"integer","title":"Outreach Sent","default":0},"previously_invited":{"type":"integer","title":"Previously Invited","default":0},"invite_removed":{"type":"integer","title":"Invite Removed","default":0},"skipped":{"type":"integer","title":"Skipped","default":0},"unknown":{"type":"integer","title":"Unknown","default":0}},"type":"object","title":"AutomationCreatorsSummary","description":"Outcome counts across the FULL result set (not just the page)."},"AutomationDetailResponse":{"properties":{"data":{"$ref":"#/components/schemas/AutomationListItem"},"creators_remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Creators Remaining"},"currency":{"type":"string","title":"Currency"}},"type":"object","required":["data","currency"],"title":"AutomationDetailResponse"},"AutomationDmRequest":{"properties":{"automation_name":{"type":"string","maxLength":120,"minLength":1,"title":"Automation Name"},"mode":{"type":"string","enum":["vanilla","with_image","with_product_card","spark_code"],"title":"Mode","default":"vanilla"},"schedule":{"$ref":"#/components/schemas/AutomationSchedule"},"creators_to_include":{"$ref":"#/components/schemas/CreatorsToInclude"},"creators_to_exclude":{"$ref":"#/components/schemas/CreatorsToExclude"},"messages":{"items":{"$ref":"#/components/schemas/MessageAddon"},"type":"array","maxItems":5,"minItems":1,"title":"Messages","description":"Initial-outreach addons — text + optional image / product card / spark_code form. ALL items are sent immediately together when the automation engages a creator. Use `follow_ups` for delayed steps."},"follow_ups":{"items":{"$ref":"#/components/schemas/FollowUpStep"},"type":"array","maxItems":3,"title":"Follow Ups","description":"Delayed follow-up steps — same shape as TC."},"dm_config":{"anyOf":[{"$ref":"#/components/schemas/DmConfig"},{"type":"null"}]},"personalization":{"anyOf":[{"$ref":"#/components/schemas/DmPersonalizationConfig"},{"type":"null"}],"description":"CORE-4857: enable AI personalization of the initial-outreach DM. Omit to send the message exactly as written. When set, the send engine rewrites the DM per creator from the [Block] tokens in your message text and falls back to `fallback_message` for low-signal creators. See DmPersonalizationConfig."},"ai_enabled":{"type":"boolean","title":"Ai Enabled","default":false},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"is_evergreen":{"type":"boolean","title":"Is Evergreen","description":"When true, the automation continuously re-evaluates its `creators_to_include.filters` for new qualifying creators on each run. No effect when `creators_to_include.crm_group_id` is set (CRM groups are inherently evergreen). Maps to top-level `Automations.is_evergreen` column.","default":false},"business_hours_timezone":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Business Hours Timezone","description":"When set, the runtime restricts outbound message delivery (initial outreach + follow-ups) to 9am–9pm in this IANA timezone (e.g. `America/Los_Angeles`). Null disables the restriction. The 9–9 window is fixed; only the timezone is configurable. Distinct from `schedule.timezone`."}},"additionalProperties":false,"type":"object","required":["automation_name","schedule","messages"],"title":"AutomationDmRequest","description":"Body for POST /automations/dm.\n\nModes (drives DB `automation_type` mapping):\n  * `vanilla`           → `Message`\n  * `with_image`        → `Message + Image`            (requires image addon)\n  * `with_product_card` → `Message + Product Card`     (requires product_card addon)\n  * `spark_code`        → `Spark Code`                 (requires spark_code config)\n\nThe legacy `refundable_sample_request` mode (`Sample Request Processing`\ninternal type) was REMOVED in the post-frontend-audit revision —\nproduction data showed zero rows actually carry the `refundable_sample`\nconfig sub-key the model wrote, so the field was dead weight. Customers\nwanting that flow should use either:\n  * `dm` mode `with_product_card` for a product-card DM, OR\n  * the top-level `sample_request` type for full auto-approval logic."},"AutomationEmailRequest":{"properties":{"automation_name":{"type":"string","maxLength":120,"minLength":1,"title":"Automation Name"},"email_account_id":{"type":"integer","exclusiveMinimum":0.0,"title":"Email Account Id","description":"Pre-connected email account. Each account links to at most ONE active email automation per shop."},"subject":{"type":"string","maxLength":255,"minLength":1,"title":"Subject"},"body":{"type":"string","maxLength":50000,"minLength":1,"title":"Body","description":"HTML or plaintext."},"selection_mode":{"type":"string","enum":["mailing_list","filters"],"title":"Selection Mode"},"mailing_list":{"anyOf":[{"$ref":"#/components/schemas/EmailMailingList"},{"type":"null"}]},"filters":{"anyOf":[{"$ref":"#/components/schemas/EmailFilters"},{"type":"null"}]},"creators_to_exclude":{"$ref":"#/components/schemas/CreatorsToExclude"},"attachments":{"items":{"$ref":"#/components/schemas/EmailAttachment"},"type":"array","maxItems":10,"title":"Attachments","description":"Email attachments — `[{name, url}]` shape. URL must be a hosted HTTPS URL (no file uploads). Server fetches at create time and re-hosts internally."}},"additionalProperties":false,"type":"object","required":["automation_name","email_account_id","subject","body","selection_mode"],"title":"AutomationEmailRequest","description":"Body for POST /automations/email.\n\nConstraint: `email_account_id` must NOT be linked to another active email\nautomation in this shop. Backend returns 409 EMAIL_ACCOUNT_ALREADY_LINKED\nwith the conflicting automation_id.\n\nNOTE: schedule, end_date, and email_sent_per_day are NOT supported on\nemail automations in v1 — the underlying `email_automations` table has\nno columns for them and silently dropping them in the contract would\nlie to callers about what's persisted. v1.1 will add the columns +\nexpose these fields. Email automation timing is currently governed by\nportal-level defaults (one cycle per `next_run_cycle`).\nPer PR #1513 round-5 review (cursor + coderabbitai)."},"AutomationListItem":{"properties":{"automation_id":{"type":"integer","title":"Automation Id"},"automation_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Automation Name"},"automation_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Automation Type"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Display status from map_automation_status()"},"status_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Message"},"gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gmv"},"sample_requests":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sample Requests","description":"DB: automation_metrics.sample_request (singular)"},"accepted_requests":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Accepted Requests","description":"DB: automation_metrics.accepted_request (singular)"},"videos_posted":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Videos Posted"},"videos_converted":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Videos Converted"},"creators_reached":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Creators Reached","description":"**Per-automation** distinct creators reached by THIS automation. Lifetime when the request has no `start_date`/`end_date`; windowed when a date filter is supplied. For the **shop-wide** windowed total that matches the portal's Outreach Metrics dashboard, use the top-level ``aggregate.creators_reached`` field — it's a different number computed from the agency rollup table and won't equal the sum of these per-row values."},"dm_response_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Dm Response Count","description":"DB: automation_metrics.dm_response_count — unique creators who replied to DMs (30-day rolling)"},"reply_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Reply Rate","description":"Computed: (dm_response_count / creators_reached) * 100. Populated for Running, Stopped, and Completed automations. Null when creators_reached is 0."},"skipped":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Skipped","description":"**Per-automation** creators the worker evaluated but did NOT reach (handle unresolved, outreach-limit blocked, previously invited, below criteria, etc.). Sourced from the same stats view as ``creators_reached`` / ``total_creators``: that view only retains rows for automations with recent activity, so all three read **null (not 0)** for automations idle for more than ~a day — null means 'no stats row', not 'zero skips'. For the always-available, per-creator breakdown of WHO was skipped and WHY, call ``GET /public/v1/automations/{automation_id}/creators``."},"total_creators":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Creators","description":"**Per-automation** creators processed (reached + skipped) — NOT the audience size. 0 until sending begins; see ``creators_remaining`` for the resolved audience.\n\nNote: this is a per-row number scoped to ONE automation. Summing across rows will exceed the portal's shop-wide 'Total Creators' headline because the dashboard counts distinct creators-per-day across the whole shop, then sums days. For that headline number, read ``aggregate.tc_invites_creator_count`` on the top-level response."},"creators_remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Creators Remaining","description":"Resolved audience still to be processed. This is what confirms the audience resolved (e.g. your uploaded/CRM-group creators). Populated right after the automation is started."},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed At"},"created_via":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created Via","description":"Surface that created this automation. Always populated: rows with NULL/empty `created_via` (predating migration 020) surface as `portal`. Common values: `portal` | `api` | `api_direct` | `mcp` | `agent` | `slack` | `internal` | `campaign_tc_auto` (and other internal surfaces may appear — the set is not closed; unknown values are returned verbatim and remain filterable). **`api_direct`** marks a single-creator Target Collab automation created by `POST /public/v1/target-collabs` (the direct-invite convenience surface) — these are named `API direct invite — {handle}`. Filter the list by `created_via` / `exclude_created_via` to include or hide them."}},"type":"object","required":["automation_id"],"title":"AutomationListItem"},"AutomationMessagesTemplateResponse":{"properties":{"automation_id":{"type":"integer","title":"Automation Id"},"automation_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Automation Name"},"automation_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Automation Type"},"tc_invite_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tc Invite Message","description":"config.TARGET_COLLAB_MESSAGE — TC invite template (TC types only)"},"message_sequence":{"items":{},"type":"array","title":"Message Sequence","description":"config.addons — ordered DM sequence; array order is send order"},"follow_up_steps":{"items":{},"type":"array","title":"Follow Up Steps"},"ai_custom_qa":{"items":{},"type":"array","title":"Ai Custom Qa"},"personalization":{"anyOf":[{"$ref":"#/components/schemas/AutomationPersonalizationConfig"},{"type":"null"}],"description":"CORE-4857 AI personalization config, when the automation has it (DM types only). Null otherwise. Configure via POST /automations/dm; use POST /personalization/* to preview."}},"type":"object","required":["automation_id"],"title":"AutomationMessagesTemplateResponse","description":"Message templates configured on one automation (CORE-5593).\n\nThis is the *configured* copy — placeholders like ``{{creators username}}``\nor ``{creator_name}`` are not substituted. For the exact per-creator text\nthat was actually sent, use ``GET /automations/{id}/sent-messages``."},"AutomationPersonalizationConfig":{"properties":{"enabled":{"type":"boolean","title":"Enabled","description":"Whether the send engine personalizes this automation's DM.","default":false},"tone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tone","description":"AI rewrite tone (engaging | bold | inspirational)."},"fallback_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fallback Message","description":"Sent verbatim (name/product blocks filled) to creators with too little signal to personalize."},"offer_discussion":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Offer Discussion","description":"[Offer Discussion] block value."},"product_description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Description","description":"[Product Description] block value."},"product_selling_points":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Selling Points","description":"[Product Selling Points] block value."}},"type":"object","title":"AutomationPersonalizationConfig","description":"CORE-4857 — the AI-personalization config on a DM automation, read back.\n\nProjected from ``config.PERSONALIZATION`` (the block the send engine reads).\nPresent only when personalization was configured — via the portal composer\nor via ``POST /automations/dm`` with a ``personalization`` object. ``null``\non automations that don't use it. Product/offer strings are the fixed values\nthe brand approved; the Content/Product-Discussion blocks are AI-generated\nper creator at send time and so aren't stored here."},"AutomationResourceV2":{"properties":{"automation_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Automation Id","description":"None on dry-run."},"shop_id":{"type":"integer","title":"Shop Id"},"automation_name":{"type":"string","title":"Automation Name"},"automation_type":{"type":"string","title":"Automation Type","description":"One of: target_collab | dm | email | sample_request."},"automation_status":{"type":"string","title":"Automation Status"},"state":{"type":"string","title":"State"},"config":{"additionalProperties":true,"type":"object","title":"Config","description":"Full config as stored. Type-specific."},"schedule":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Schedule"},"creators_to_include":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Creators To Include"},"creators_to_exclude":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Creators To Exclude"},"crm_group_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Crm Group Id"},"ai_enabled":{"type":"boolean","title":"Ai Enabled","default":false},"end_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At"},"created_via":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created Via","description":"Surface that created the automation. Always populated on response: rows with NULL or empty `created_via` (predating migration 020) are surfaced as `portal` since the portal was historically the only writer. Common values: portal | api | api_direct | mcp | agent | slack | internal | campaign_tc_auto (set is not closed — other internal surfaces may appear). `api_direct` marks single-creator invites created by `POST /public/v1/target-collabs`."},"side_effects":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Side Effects","description":"Backend side-effects, e.g. {'crm_group_created': '<uuid>'} on sample_request creates."}},"type":"object","required":["shop_id","automation_name","automation_type","automation_status","state"],"title":"AutomationResourceV2","description":"Canonical write-endpoint response. Replaces the v1 AutomationResource\n(which mirrored the legacy single-shape body). Now reflects the\nfour-type taxonomy — `automation_type` is the public-API type, not the\nDB string. The DB string is in `_internal_type` for debugging only."},"AutomationSampleRequestRequest":{"properties":{"automation_name":{"type":"string","maxLength":120,"minLength":1,"title":"Automation Name"},"sample_request":{"$ref":"#/components/schemas/SampleRequestPayload"},"schedule":{"anyOf":[{"$ref":"#/components/schemas/AutomationSchedule"},{"type":"null"}],"description":"Optional — sample_request can run continuously. If set, the automation only auto-approves/rejects during scheduled hours."},"creators_to_exclude":{"$ref":"#/components/schemas/CreatorsToExclude","description":"Optional creator exclusion. Persists as `config.excluded_creators` (sibling of `sample_request_config`) using `{excluded_lists_ids, excluded_crm_groups_ids, excluded_creator_ids}` shape — matches reacher-web-react formDataTransformer.ts."}},"additionalProperties":false,"type":"object","required":["automation_name","sample_request"],"title":"AutomationSampleRequestRequest","description":"Body for POST /automations/sample-request.\n\nMaps to DB `automation_type='New Sample Request Processing'`. Backend\nside-effect: a CRM group is created from `minimum_performance_criteria`\nso creator membership stays dynamic. The created `crm_group_id` is\nreturned in the response for reference."},"AutomationSchedule":{"properties":{"Monday_maxCreators":{"type":"integer","maximum":10000.0,"minimum":0.0,"title":"Monday Maxcreators","default":0},"Tuesday_maxCreators":{"type":"integer","maximum":10000.0,"minimum":0.0,"title":"Tuesday Maxcreators","default":0},"Wednesday_maxCreators":{"type":"integer","maximum":10000.0,"minimum":0.0,"title":"Wednesday Maxcreators","default":0},"Thursday_maxCreators":{"type":"integer","maximum":10000.0,"minimum":0.0,"title":"Thursday Maxcreators","default":0},"Friday_maxCreators":{"type":"integer","maximum":10000.0,"minimum":0.0,"title":"Friday Maxcreators","default":0},"Saturday_maxCreators":{"type":"integer","maximum":10000.0,"minimum":0.0,"title":"Saturday Maxcreators","default":0},"Sunday_maxCreators":{"type":"integer","maximum":10000.0,"minimum":0.0,"title":"Sunday Maxcreators","default":0},"start_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Time","description":"HH:MM 24-hour, in `timezone`. Hours 00-23, minutes 00-59."},"end_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Time","description":"HH:MM 24-hour, in `timezone`. Hours 00-23, minutes 00-59."},"timezone":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Timezone","description":"IANA timezone name, e.g. America/Los_Angeles"}},"additionalProperties":false,"type":"object","title":"AutomationSchedule","description":"Per-day creator-outreach caps + run window. Stored into Automations.schedule\nJSONB. Same shape as the existing portal payload — keeps the portal and\nPublic API agreeing on schema."},"AutomationSentMessagesResponse":{"properties":{"automation_id":{"type":"integer","title":"Automation Id"},"automation_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Automation Name"},"automation_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Automation Type"},"template":{"$ref":"#/components/schemas/AutomationMessagesTemplateResponse"},"dm_sent":{"$ref":"#/components/schemas/SentDmSection"},"tc_sent":{"$ref":"#/components/schemas/SentTcSection"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message","description":"Explicit empty state when the automation has sent nothing at all"}},"type":"object","required":["automation_id","template","dm_sent","tc_sent"],"title":"AutomationSentMessagesResponse","description":"Exact sent copy for one automation, grouped by creator (DM) and\ninvitation batch (TC), with the campaign link where one exists."},"AutomationTargetCollabRequest":{"properties":{"automation_name":{"type":"string","maxLength":120,"minLength":1,"title":"Automation Name"},"schedule":{"$ref":"#/components/schemas/AutomationSchedule"},"creators_to_include":{"$ref":"#/components/schemas/CreatorsToInclude"},"creators_to_exclude":{"$ref":"#/components/schemas/CreatorsToExclude"},"target_collab":{"$ref":"#/components/schemas/TargetCollabConfig"},"messages":{"items":{"$ref":"#/components/schemas/MessageAddon"},"type":"array","maxItems":5,"title":"Messages","description":"Initial-outreach addons — text, images, product cards, etc. ALL items in this list are sent immediately together when the automation engages a creator. Use `follow_ups` for delayed follow-up steps. Empty messages = invite-only TC."},"follow_ups":{"items":{"$ref":"#/components/schemas/FollowUpStep"},"type":"array","maxItems":3,"title":"Follow Ups","description":"Delayed follow-up steps. Each step has `delay_days` after the prior step (or after the initial outreach for the first follow-up) and one or more addons that fire together. Use this to build a multi-touch sequence."},"ai_enabled":{"type":"boolean","title":"Ai Enabled","default":false},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"auto_resolve_conflicts":{"type":"string","enum":["SKIP_ALL","MOVE_NOT_ACCEPTED","MOVE_ALL"],"title":"Auto Resolve Conflicts","description":"How the runtime handles creators who already have a pending TC invite from another automation. SKIP_ALL leaves them untouched; MOVE_NOT_ACCEPTED (default — matches portal-created automations) re-targets creators whose prior invite was never accepted; MOVE_ALL re-targets regardless of prior invite state. Maps to `config.AUTO_RESOLVE_CONFLICTS`.","default":"MOVE_NOT_ACCEPTED"},"is_evergreen":{"type":"boolean","title":"Is Evergreen","description":"When true, the automation continuously re-evaluates its `creators_to_include.filters` and queues newly-qualifying creators on each run. Has no effect for `lists` mode (lists are static) or `crm_group` mode (CRM groups are inherently evergreen). Maps to top-level `Automations.is_evergreen` column.","default":false},"business_hours_timezone":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Business Hours Timezone","description":"When set, the runtime restricts outbound message delivery (initial outreach + follow-ups) to 9am–9pm in this IANA timezone (e.g. `America/Los_Angeles`). Null disables the restriction — messages can fire at any time within the schedule's daily window. The 9–9 window is fixed; only the timezone is configurable. Distinct from `schedule.timezone`, which governs *when the automation evaluates its queue* (per CORE-3031)."}},"additionalProperties":false,"type":"object","required":["automation_name","schedule","target_collab"],"title":"AutomationTargetCollabRequest","description":"Body for POST /automations/target-collab.\n\nMaps internally to DB `automation_type='Target Collab'` (or a TC variant\nif `messages` is present, in which case the route stamps\n`Message + Target Collab + Target Collab Card`).\n\nThe TC Cleanup variant has its own top-level type\n(`AutomationTcCleanupRequest` → `POST /automations/tc-cleanup`)\nrather than living here as a flag. Reasoning: cleanup is structurally\ndifferent — no messages / follow-ups, no custom invite name,\nproducts-only — and surfacing it as a TC mode forced callers to\nsubmit fields the runtime ignores. Per architecture review 2026-05-01."},"AutomationTcCleanupRequest":{"properties":{"automation_name":{"type":"string","maxLength":120,"minLength":1,"title":"Automation Name"},"schedule":{"$ref":"#/components/schemas/AutomationSchedule"},"creators_to_include":{"$ref":"#/components/schemas/CreatorsToInclude"},"creators_to_exclude":{"$ref":"#/components/schemas/CreatorsToExclude"},"products":{"items":{"$ref":"#/components/schemas/TargetCollabProduct"},"type":"array","maxItems":50,"minItems":1,"title":"Products","description":"Products to re-invite the matching creators for. Cleanup uses the same TargetCollabProduct shape (product_id + commission rates) as a regular TC. Required — runtime needs at least one product to issue the re-invite."},"invite_start_before_days":{"anyOf":[{"type":"integer","maximum":365.0,"minimum":1.0},{"type":"null"}],"title":"Invite Start Before Days","description":"Re-target only creators whose original TC invite was sent at least N days ago. Null = no lower bound on invite age. Persists as `config.INVITE_START_BEFORE_DAYS`."},"invite_expire_after_days":{"anyOf":[{"type":"integer","maximum":365.0,"minimum":1.0},{"type":"null"}],"title":"Invite Expire After Days","description":"Re-target only creators whose original TC invite expires within the next N days. Null = no upper bound on time-to-expiry. Persists as `config.INVITE_EXPIRE_AFTER_DAYS`."},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"}},"additionalProperties":false,"type":"object","required":["automation_name","schedule","products"],"title":"AutomationTcCleanupRequest","description":"Body for POST /automations/tc-cleanup.\n\nA utility automation that re-targets creators in this shop who\nreceived a TC invite from a prior automation but didn't accept it.\nThe runtime walks unaccepted-invite state shop-wide and re-invites\nmatching creators with the products supplied here.\n\nConceptually closer to `sample_request` (auto-process based on\ncriteria) than to the proactive TC outreach types — no messages,\nno follow-ups. Maps to DB `automation_type='TC Cleanup'`."},"AutomationUpdateRequest":{"properties":{"automation_name":{"anyOf":[{"type":"string","maxLength":120,"minLength":1},{"type":"null"}],"title":"Automation Name"},"schedule":{"anyOf":[{"$ref":"#/components/schemas/AutomationSchedule"},{"type":"null"}]},"creators_to_include":{"anyOf":[{"$ref":"#/components/schemas/CreatorsToInclude"},{"type":"null"}]},"creators_to_exclude":{"anyOf":[{"$ref":"#/components/schemas/CreatorsToExclude"},{"type":"null"}]},"messages":{"anyOf":[{"items":{"$ref":"#/components/schemas/MessageAddon"},"type":"array","maxItems":5},{"type":"null"}],"title":"Messages","description":"Replace the initial-outreach addon set. ALL items are sent immediately together when the automation engages a creator. Pass `[]` to clear messages (invite-only). Use `follow_ups` for delayed sequencing. Only valid for `target_collab` and `dm` automations."},"follow_ups":{"anyOf":[{"items":{"$ref":"#/components/schemas/FollowUpStep"},"type":"array","maxItems":3},{"type":"null"}],"title":"Follow Ups","description":"Replace the delayed follow-up sequence. Pass `[]` to clear. Only valid for `target_collab` and `dm` automations."},"target_collab":{"anyOf":[{"$ref":"#/components/schemas/TargetCollabPatchPayload"},{"type":"null"}],"description":"Partial update for the TC-specific config. Send only the keys you want to change — supported sub-keys mirror `TargetCollabConfig`: `invitation_name`, `valid_until`, `message`, `products`, `content_type`, `sample_policy`, `support_contact`, `lock_receivers_profiles`. Unknown keys are rejected with 422 (extra='forbid'). The translator merges into the existing `config` blob and dual-writes to the legacy UPPERCASE root."},"dm_config":{"anyOf":[{"$ref":"#/components/schemas/DmConfigPatchPayload"},{"type":"null"}],"description":"Partial update for the DM-specific config. Currently supports `spark_code` only (other dm_config keys were removed 2026-05-01)."},"sample_request":{"anyOf":[{"$ref":"#/components/schemas/SampleRequestPatchPayload"},{"type":"null"}],"description":"Partial update for the sample-request config. Send only the keys you want to change — supported sub-keys mirror `SampleRequestPayload`: `action`, `rejection_reason`, `approval_message`, `rejection_message`, `message_images`, `products`, `minimum_performance_criteria`, `maximum_performance_criteria`, `auto_approval_settings`, `target_list`. Unknown keys (top-level OR nested) are rejected with 422 (extra='forbid'). The translator routes both criteria blocks through the V2 prefix + percent-scale helper."},"ai_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Ai Enabled"},"is_evergreen":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Evergreen"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"business_hours_timezone":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Business Hours Timezone","description":"Set or change the 9am–9pm send-window timezone (CORE-3031). Pass an IANA name to enable, or the empty string `\"\"` to disable. Only valid for `target_collab` and `dm` automations."},"email_sent_per_day":{"anyOf":[{"type":"integer","maximum":500.0,"minimum":1.0},{"type":"null"}],"title":"Email Sent Per Day"},"subject":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Subject"},"body":{"anyOf":[{"type":"string","maxLength":50000,"minLength":1},{"type":"null"}],"title":"Body"}},"additionalProperties":false,"type":"object","title":"AutomationUpdateRequest","description":"PATCH /automations/{id} — partial update across all 4 types.\n\nCannot change type / lifecycle fields / system fields. The set of\nlegitimately mutable fields varies by type, but for v1 we accept the\nunion and let the route validate type-specific constraints."},"AutomationUpdateRequestPublic":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name"},"trigger_frequency":{"anyOf":[{"$ref":"#/components/schemas/TriggerFrequency"},{"type":"null"}]},"campaign_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Campaign Ids"},"conditions":{"anyOf":[{"items":{"$ref":"#/components/schemas/ConditionRulePublic"},"type":"array"},{"type":"null"}],"title":"Conditions"},"actions":{"anyOf":[{"items":{"$ref":"#/components/schemas/ActionRulePublic"},"type":"array"},{"type":"null"}],"title":"Actions"},"guardrails":{"anyOf":[{"items":{"$ref":"#/components/schemas/GuardrailRulePublic"},"type":"array"},{"type":"null"}],"title":"Guardrails"},"boost_start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Boost Start Date"},"boost_end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Boost End Date"}},"additionalProperties":false,"type":"object","title":"AutomationUpdateRequestPublic","description":"``PUT /public/v1/gmv-max/automations/{id}`` — patch an automation.\n\nAll fields optional; only fields the caller actually sets are persisted\n(PATCH semantics via ``model_dump(exclude_unset=True)`` in the route).\nMirrors ``GmvMaxModels.AutomationUpdateRequest`` semantics — the\nactions/guardrails cross-validator runs only when both are sent, the\ncontroller re-validates against the persisted row when only one side\nof the pair is updated."},"AutomationWriteResponseV2":{"properties":{"data":{"$ref":"#/components/schemas/AutomationResourceV2"},"dry_run":{"type":"boolean","title":"Dry Run","default":false},"side_effects":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Side Effects","description":"Backend side-effects, e.g. {'crm_group_created': 'uuid-...'} for sample_request creates."}},"type":"object","required":["data"],"title":"AutomationWriteResponseV2"},"AutomationsListAggregate":{"properties":{"creators_reached":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Creators Reached","description":"Shop-wide distinct creators reached, summed across daily rollups in the window. Matches the portal's 'Creators Reached' column. NOT a global distinct count — a creator reached on multiple days is counted on each."},"tc_invites":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tc Invites","description":"Shop-wide count of Target Collab invitations sent, summed across daily rollups in the window. Matches the portal's 'Target Collabs' column."},"tc_invites_creator_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tc Invites Creator Count","description":"Shop-wide distinct creators in Target Collab creator-list arrays, computed per day and summed across the window. Matches the portal dashboard's 'Total Creators' sub-label under Target Collabs — this is the field that customers comparing API to UI most commonly expect."},"creators_messaged":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Creators Messaged"},"emails_sent":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Emails Sent"},"sample_request":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sample Request"},"sample_approved":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sample Approved"},"videos_posted":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Videos Posted"},"videos_converted":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Videos Converted"},"spark_codes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Spark Codes"},"added_showcase_affiliates":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Added Showcase Affiliates"},"accepted_tc_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Accepted Tc Count"},"tc_showcase_creator_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tc Showcase Creator Count"},"tc_content_creator_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tc Content Creator Count"},"tc_acceptance_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Tc Acceptance Rate","description":"Average TC acceptance rate across the daily rollups in the window (mean of daily rates, not creator-weighted). Mirrors the portal dashboard's computation."}},"additionalProperties":true,"type":"object","title":"AutomationsListAggregate","description":"Shop-wide windowed metric totals matching the portal's Outreach\nMetrics dashboard. Sourced from ``customers.agency_dashboard_table``\n(a daily pre-aggregated rollup) and SUMmed across the daily rows\ninside the requested window — same computation the portal UI does.\nSHOP-WIDE — NOT per-automation. Counted DISTINCT per day, then\nsummed across days; a creator reached on Tue AND Wed counts twice.\n\nPopulated only when ``start_date`` and/or ``end_date`` is supplied\non the request (the aggregate is window-scoped by design). ``None``\non unfiltered calls.\n\nMapping to the portal's \"Outreach Metrics\" card columns:\n\n- ``creators_reached`` → \"Creators Reached\"\n- ``tc_invites`` → \"Target Collabs\"\n- ``tc_invites_creator_count`` → \"Total Creators\" sub-label\n  under Target Collabs"},"AutomationsListPaginatedResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/AutomationListItem"},"type":"array","title":"Data"},"currency":{"type":"string","title":"Currency"},"pagination":{"title":"Pagination"},"date_range":{"anyOf":[{"$ref":"#/components/schemas/DateRange"},{"type":"null"}]},"shops_queried":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Shops Queried","description":"Shop names included when querying multiple shops (x-shop-id: all or comma-separated)."},"aggregate":{"anyOf":[{"$ref":"#/components/schemas/AutomationsListAggregate"},{"type":"null"}],"description":"Shop-wide windowed metric totals — populated only when a date filter is supplied. These are NOT sums of the per-row fields above; they come from the same agency rollup the portal Outreach Metrics dashboard reads. Use this object when you want numbers that match the portal UI exactly."}},"type":"object","required":["data","currency","pagination"],"title":"AutomationsListPaginatedResponse"},"AutomationsListRequest":{"properties":{"page":{"type":"integer","minimum":1.0,"title":"Page","default":1},"page_size":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Page Size","default":50},"sort_by":{"anyOf":[{"type":"string","enum":["gmv","created_at","completed_at","creators_reached","dm_response_count"]},{"type":"null"}],"title":"Sort By"},"sort_dir":{"type":"string","enum":["asc","desc"],"title":"Sort Dir","default":"desc"},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date","description":"Inclusive window start (YYYY-MM-DD). When supplied (alone or with ``end_date``), ``creators_reached`` / ``skipped`` / ``total_creators`` switch from LIFETIME materialized-view values to in-window DISTINCT counts against the raw event tables. Filters the row set on ``Automation.created_at`` as well."},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date","description":"Inclusive window end (YYYY-MM-DD). When supplied (alone or with ``start_date``), see ``start_date`` for the creator-metric semantic change."},"automation_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Automation Type"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"created_via":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Created Via","description":"Include only automations created by these surfaces. Common values: `portal` | `api` | `api_direct` | `mcp` | `agent` | `slack` | `internal` | `campaign_tc_auto` (set is not closed). NULL/empty provenance matches `portal`. Values not present in the shop's automation data match nothing; future/internal values remain filterable when they appear in the data. Example: `[\"api_direct\"]` returns only single-creator direct invites."},"exclude_created_via":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Exclude Created Via","description":"Exclude automations created by these surfaces. Applied after `created_via`. Example: `[\"api_direct\"]` hides the `API direct invite — {handle}` rows produced by `POST /public/v1/target-collabs` so the list shows only your managed automations."}},"additionalProperties":false,"type":"object","title":"AutomationsListRequest","description":"POST /automations/list request body.\n\nWhen ``start_date`` and/or ``end_date`` is supplied:\n\n1. The ``creators_reached``, ``skipped``, and ``total_creators``\n   fields on every returned automation row are computed as\n   DISTINCT creators reached/skipped WITHIN the requested window —\n   not the lifetime aggregates that the unfiltered call returns.\n2. The response includes a top-level ``aggregate`` object with\n   shop-wide windowed totals sourced from\n   ``customers.agency_dashboard_table`` — the same daily rollup\n   the portal Outreach Metrics dashboard reads. Use\n   ``aggregate.creators_reached`` /\n   ``aggregate.tc_invites_creator_count`` etc. when you want\n   numbers that match the portal UI exactly; the per-row fields\n   are scoped per-automation and their sums won't equal the\n   dashboard headlines.\n\nSee CORE-4861 for the history of why the API and UI used to\ndisagree on these numbers."},"CampaignArchivedResponse":{"properties":{"campaign_id":{"type":"integer","title":"Campaign Id"},"status":{"type":"string","title":"Status","default":"archived"}},"type":"object","required":["campaign_id"],"title":"CampaignArchivedResponse","description":"200 response for ``POST /campaigns/{id}/archive``."},"CampaignCreateRequest":{"properties":{"campaign_name":{"type":"string","maxLength":255,"title":"Campaign Name"},"campaign_type":{"$ref":"#/components/schemas/CampaignType"},"creator_source":{"$ref":"#/components/schemas/CreatorSource"},"start_date":{"type":"string","title":"Start Date"},"end_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date","description":"YYYY-MM-DD. REQUIRED for challenge / leaderboard / live_gmv_challenge campaigns (the portal launch form requires an end date for these); optional only for retainer campaigns, which may run open-ended."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"REQUIRED for launch. Rich-text (HTML) campaign description shown to creators. Must contain visible text — an empty string or HTML-only value like '<p></p>' is rejected, matching the portal launch form."},"status":{"$ref":"#/components/schemas/CampaignStatus","default":"published"},"pricing":{"oneOf":[{"$ref":"#/components/schemas/RetainerPricing"},{"$ref":"#/components/schemas/ChallengePricing"},{"$ref":"#/components/schemas/LeaderboardPricing"},{"$ref":"#/components/schemas/LiveGMVChallengePricing"}],"title":"Pricing","discriminator":{"propertyName":"campaign_type","mapping":{"challenge":"#/components/schemas/ChallengePricing","leaderboard":"#/components/schemas/LeaderboardPricing","live_gmv_challenge":"#/components/schemas/LiveGMVChallengePricing","retainer":"#/components/schemas/RetainerPricing"}}},"products":{"anyOf":[{"$ref":"#/components/schemas/CampaignProducts"},{"type":"null"}],"description":"REQUIRED for launch. The portal launch form needs at least one product: send products.all_selected=true to include every shop product, or products.items=[...] with 1-10 products."},"creators":{"anyOf":[{"items":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}]},"type":"object"},"type":"array"},{"type":"null"}],"title":"Creators","description":"Optional list of creators to seed into the campaign. Mirrors the portal's contract exactly (see `community.schemas.campaign.CampaignCreate.creators`) so the public API doesn't fork from `/api/marketplace/v2`. Each dict is opaque to validation but is typically `{\"creator_id\": int, \"tier\": str | None, \"payout_override\": int | None}`. Pass `null` or omit for the common case of \"no seeded creators\" (production fixtures all do this; creators are added later via the accept/reject flow)."},"window_url":{"anyOf":[{"type":"string","maxLength":2048},{"type":"null"}],"title":"Window Url","description":"HTTPS URL — public landing/preview page for this campaign."},"is_available_to_all":{"type":"boolean","title":"Is Available To All","default":false},"requires_approval":{"type":"boolean","title":"Requires Approval","default":false},"creator_level":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"string"},{"type":"null"}],"title":"Creator Level","description":"Creator tier filter (list or CSV string)."},"support_email":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Support Email"},"short_header_message":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Short Header Message","description":"REQUIRED for launch. Short headline shown on the campaign card. Must be a non-empty string — the portal launch form rejects a blank value."},"content_guidelines":{"anyOf":[{"additionalProperties":{"items":{"type":"string"},"type":"array"},"type":"object"},{"type":"null"}],"title":"Content Guidelines"},"inspiration_links":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":20},{"type":"null"}],"title":"Inspiration Links","description":"List of HTTPS URLs (inspiration videos, mood-boards, etc.)."},"has_tiered_payout":{"type":"boolean","title":"Has Tiered Payout","default":false},"payout_structure":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Payout Structure"},"grace_period":{"anyOf":[{"type":"integer","maximum":365.0,"minimum":0.0},{"type":"null"}],"title":"Grace Period"},"reminders":{"anyOf":[{"$ref":"#/components/schemas/CampaignReminders"},{"type":"null"}]},"email_templates":{"anyOf":[{"$ref":"#/components/schemas/CampaignEmailTemplates"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["campaign_name","campaign_type","creator_source","start_date","pricing"],"title":"CampaignCreateRequest","description":"``POST /public/v1/campaigns`` — create a campaign.\n\nMirrors ``community.schemas.campaign.CampaignCreateRequest`` minus\n``shop_id`` (injected from the ``x-shop-id`` header).\n\nImage fields here are URL strings only — the portal accepts\nmultipart file uploads, but public-API agents already have URLs\n(uploaded via their own CDN or the portal). HTTPS-only enforced."},"CampaignCreatedResponse":{"properties":{"campaign_id":{"type":"integer","title":"Campaign Id"},"uuid_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Uuid Id"},"status":{"type":"string","title":"Status","default":"created"},"invite_link":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invite Link","description":"Public invite URL the caller can share with creators to join this campaign. Subdomain is picked by ``creator_source``: ``social_army`` campaigns (legacy partnership) use ``https://soco.reacherapp.com``; every other source — ``my_community`` and the rest — uses ``https://creator.reacherapp.com``. Full pattern: ``{base}/campaigns/{shop_id}/{uuid_id}``. Always populated on a successful 201. Falsy only in the unexpected case where the service returned without setting ``uuid_id``. Available on every campaign endpoint that returns a campaign payload: ``POST /campaigns``, ``GET /campaigns``, ``GET /campaigns/{id}``, ``GET /campaigns/{id}/detail`` — uniform contract."}},"type":"object","required":["campaign_id"],"title":"CampaignCreatedResponse","description":"201 response for ``POST /campaigns``."},"CampaignDeletedResponse":{"properties":{"campaign_id":{"type":"integer","title":"Campaign Id"},"status":{"type":"string","title":"Status","default":"deleted"}},"type":"object","required":["campaign_id"],"title":"CampaignDeletedResponse","description":"200 response for ``DELETE /campaigns/{id}``."},"CampaignEmailTemplates":{"properties":{"enabled":{"type":"boolean","title":"Enabled","default":true},"application":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Application"},"acceptance":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Acceptance"},"rejection":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Rejection"}},"type":"object","title":"CampaignEmailTemplates","description":"Email template configuration for a campaign."},"CampaignProducts":{"properties":{"all_selected":{"type":"boolean","title":"All Selected","description":"Whether all shop products are selected","default":false},"items":{"items":{"$ref":"#/components/schemas/ProductItem"},"type":"array","title":"Items","description":"Selected product list"},"use_first_product_image":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Use First Product Image","description":"Use first product image as campaign image"},"custom_image":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custom Image","description":"Custom campaign image (base64 or URL)"},"campaign_image":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campaign Image","description":"Resolved campaign image URL (read-only)"}},"type":"object","title":"CampaignProducts","description":"Product configuration for a campaign."},"CampaignReminders":{"properties":{"enabled":{"type":"boolean","title":"Enabled","default":false},"message_before_3_days":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message Before 3 Days"},"message_before_1_day":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message Before 1 Day"}},"type":"object","title":"CampaignReminders","description":"Reminder configuration for a campaign."},"CampaignStatus":{"type":"string","enum":["published","draft","deleted","archived"],"title":"CampaignStatus","description":"DB-level campaign status. The frontend filter param 'active'/'past'\nare UI aliases — 'active' maps to published, 'past' maps to != published."},"CampaignType":{"type":"string","enum":["retainer","challenge","leaderboard","live_gmv_challenge"],"title":"CampaignType","description":"Supported campaign types."},"CampaignUpdateRequest":{"properties":{"campaign_name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Campaign Name"},"start_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"status":{"anyOf":[{"$ref":"#/components/schemas/CampaignStatus"},{"type":"null"}]},"pricing":{"anyOf":[{"oneOf":[{"$ref":"#/components/schemas/RetainerPricing"},{"$ref":"#/components/schemas/ChallengePricing"},{"$ref":"#/components/schemas/LeaderboardPricing"},{"$ref":"#/components/schemas/LiveGMVChallengePricing"}],"discriminator":{"propertyName":"campaign_type","mapping":{"challenge":"#/components/schemas/ChallengePricing","leaderboard":"#/components/schemas/LeaderboardPricing","live_gmv_challenge":"#/components/schemas/LiveGMVChallengePricing","retainer":"#/components/schemas/RetainerPricing"}}},{"type":"null"}],"title":"Pricing"},"products":{"anyOf":[{"$ref":"#/components/schemas/CampaignProducts"},{"type":"null"}]},"creators":{"anyOf":[{"items":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}]},"type":"object"},"type":"array"},{"type":"null"}],"title":"Creators","description":"Optional list of creators to seed into the campaign. Mirrors the portal's contract exactly (see `community.schemas.campaign.CampaignCreate.creators`) so the public API doesn't fork from `/api/marketplace/v2`. Each dict is opaque to validation but is typically `{\"creator_id\": int, \"tier\": str | None, \"payout_override\": int | None}`. Pass `null` or omit for the common case of \"no seeded creators\" (production fixtures all do this; creators are added later via the accept/reject flow)."},"window_url":{"anyOf":[{"type":"string","maxLength":2048},{"type":"null"}],"title":"Window Url"},"is_available_to_all":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Available To All"},"requires_approval":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Requires Approval"},"creator_level":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"string"},{"type":"null"}],"title":"Creator Level"},"support_email":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Support Email"},"short_header_message":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Short Header Message"},"content_guidelines":{"anyOf":[{"additionalProperties":{"items":{"type":"string"},"type":"array"},"type":"object"},{"type":"null"}],"title":"Content Guidelines"},"inspiration_links":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":20},{"type":"null"}],"title":"Inspiration Links"},"has_tiered_payout":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Tiered Payout"},"payout_structure":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Payout Structure"},"grace_period":{"anyOf":[{"type":"integer","maximum":365.0,"minimum":0.0},{"type":"null"}],"title":"Grace Period"},"reminders":{"anyOf":[{"$ref":"#/components/schemas/CampaignReminders"},{"type":"null"}]},"email_templates":{"anyOf":[{"$ref":"#/components/schemas/CampaignEmailTemplates"},{"type":"null"}]}},"additionalProperties":false,"type":"object","title":"CampaignUpdateRequest","description":"``PATCH /public/v1/campaigns/{campaign_id}``.\n\nMirrors ``community.schemas.campaign.CampaignUpdateRequest`` minus\n``shop_id`` and ``campaign_id`` (both injected — shop from header,\ncampaign from path).\n\n``campaign_type`` and ``creator_source`` are immutable post-create\nand are intentionally not in this shape; the portal rejects them\ntoo."},"CampaignUpdatedResponse":{"properties":{"campaign_id":{"type":"integer","title":"Campaign Id"},"status":{"type":"string","title":"Status","default":"updated"}},"type":"object","required":["campaign_id"],"title":"CampaignUpdatedResponse","description":"200 response for ``PATCH /campaigns/{id}``."},"ChallengePricing":{"properties":{"campaign_type":{"type":"string","const":"challenge","title":"Campaign Type","default":"challenge"},"payment_tiers":{"items":{"$ref":"#/components/schemas/PaymentTier"},"type":"array","minItems":1,"title":"Payment Tiers","description":"At least one tier required"},"reward_type":{"$ref":"#/components/schemas/RewardType","description":"Cash or custom reward"},"metric_type":{"type":"string","enum":["gmv","views","videos_posted","gmv_videos"],"title":"Metric Type","description":"Performance metric to evaluate","default":"gmv"},"minimum_gmv_required":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Minimum Gmv Required","description":"Minimum GMV floor (gmv_videos gate)"},"minimum_gmv_required_enabled":{"type":"boolean","title":"Minimum Gmv Required Enabled","description":"Whether the minimum GMV floor is active","default":false},"minimum_views_required":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Minimum Views Required","description":"Minimum views floor (when metric is views)"},"minimum_videos_required":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Minimum Videos Required","description":"Minimum videos floor (videos_posted / gmv_videos gate)"},"ranking_priority":{"anyOf":[{"type":"string","enum":["gmv","videos"]},{"type":"null"}],"title":"Ranking Priority","description":"Primary ranking metric for gmv_videos contests; the other is a per-tier requirement"}},"type":"object","required":["payment_tiers","reward_type"],"title":"ChallengePricing","description":"Pricing configuration for challenge campaigns."},"ConditionMetric":{"type":"string","enum":["creative_roi","creative_revenue","campaign_roi","campaign_revenue","campaign_cost","click_through_rate","organic_engagement_rate","product_ad_clicks","2s_video_view_rate","roi","spend","ctr","spend_pacing"],"title":"ConditionMetric","description":"Metric a condition evaluates against the campaign / creative window.\n\nThe first nine values are what the portal FE exposes in the\nCreate-Automation form. The trailing four (``roi``, ``spend``, ``ctr``,\n``spend_pacing``) appear in ``/templates`` payloads — accepted so a\ncaller that round-trips a template through ``POST /automations`` is\nnot rejected by validation."},"ConditionOperator":{"type":"string","enum":["lt","gte",">=","<=",">","<","=="],"title":"ConditionOperator","description":"Comparison operator. Both the FE's short form (``lt``, ``gte``) and\nthe legacy symbol form (``>=``, ``<``) are accepted — the controller\ntreats them as synonyms."},"ConditionRulePublic":{"properties":{"metric":{"$ref":"#/components/schemas/ConditionMetric","description":"Metric to evaluate. See `ConditionMetric` for the full set."},"operator":{"$ref":"#/components/schemas/ConditionOperator","description":"Comparison operator. `lt` / `gte` are the FE forms; `>=` / `<` etc. are legacy and equally accepted."},"value":{"type":"number","title":"Value","description":"Threshold the metric is compared against."},"lookback_days":{"type":"integer","maximum":30.0,"minimum":1.0,"title":"Lookback Days","description":"How many days of history the metric is computed over. Default 3.","default":3}},"additionalProperties":false,"type":"object","required":["metric","operator","value"],"title":"ConditionRulePublic","description":"One condition row in an automation's trigger logic."},"CreateDepositIntentRequest":{"properties":{"amount":{"type":"number","maximum":100000.0,"exclusiveMinimum":0.0,"title":"Amount"},"currency":{"type":"string","maxLength":3,"minLength":3,"title":"Currency","description":"ISO 4217 currency. The wallet's persisted currency overrides this for cross-currency safety.","default":"USD"},"success_url":{"type":"string","maxLength":2048,"title":"Success Url","description":"URL Stripe redirects to on completion. Must use a Reacher-controlled host (app.reacherapp.com, portal.reacherapp.com, staging.reacherapp.com) or localhost/127.0.0.1 for development."},"cancel_url":{"type":"string","maxLength":2048,"title":"Cancel Url"}},"additionalProperties":false,"type":"object","required":["amount","success_url","cancel_url"],"title":"CreateDepositIntentRequest","description":"``POST /public/v1/campaigns/payments/deposit-intent`` — return a\nStripe Checkout session that the caller can redirect the user to\nin order to top up the shop's marketplace wallet.\n\nCreating the intent does NOT move any money; money moves only when\nthe user completes the hosted Stripe flow and Stripe's webhook\nconfirms the deposit. Public API never sees the webhook itself."},"CreateDraftDryRunResponse":{"properties":{"dry_run":{"type":"boolean","title":"Dry Run","default":true},"would_create_for_handle":{"type":"string","title":"Would Create For Handle"},"content_type":{"type":"string","title":"Content Type"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text"},"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url"},"product_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Id"}},"type":"object","required":["would_create_for_handle","content_type"],"title":"CreateDraftDryRunResponse","description":"Shape for ``X-Dry-Run: true`` on create. Echoes what would be\npersisted without actually inserting (and without resolving the\nconversation either — pure validation echo, same posture as the\nlive-send dry-run paths)."},"CreateDraftRequest":{"properties":{"creator_handle":{"type":"string","maxLength":255,"minLength":1,"title":"Creator Handle","description":"The creator's TikTok handle (with or without leading @). Must already have an open conversation with this shop — drafts can only be staged on existing conversations, the same prerequisite TikTok enforces on the live-send path."},"text":{"anyOf":[{"type":"string","maxLength":4000,"minLength":1},{"type":"null"}],"title":"Text","description":"Plain-text reply body. Set this OR `image_url` OR `product_id` (exactly one)."},"image_url":{"anyOf":[{"type":"string","maxLength":2000,"minLength":10},{"type":"null"}],"title":"Image Url","description":"HTTPS URL of an image to send. Same SSRF + size + type constraints as the live /reply/image endpoint will apply when the human ships the draft — we do NOT fetch the URL at draft time (no outbound network call from this endpoint). Set this OR `text` OR `product_id` (exactly one)."},"product_id":{"anyOf":[{"type":"string","maxLength":64,"minLength":1},{"type":"null"}],"title":"Product Id","description":"A TikTok product_id from THIS shop's catalog. Ownership is verified ONLY when the human clicks Send in the inbox — the draft endpoint does not pre-check the catalog (the catalog can change between draft and send anyway). Set this OR `text` OR `image_url` (exactly one)."}},"additionalProperties":false,"type":"object","required":["creator_handle"],"title":"CreateDraftRequest","description":"Body for ``POST /creator-messages/draft``.\n\nExactly one of `text`, `image_url`, `product_id` must be set — matches\nthe three live-send endpoints. Validated below in `_resolve_draft_content`\nso a malformed body is rejected at the route layer with a single\nstructured 400 (rather than 5 separate model-level rules whose error\nmessages would be harder for an agent to act on).\n\n`creator_handle` is in the BODY (not the path) for the create endpoint\n— keeps the path stable as ``/creator-messages/draft`` regardless of\ncontent type. Mirrors how the existing /reply* paths are\ncreator-scoped via the URL while the create-draft surface is\ndeliberately content-shape agnostic."},"CreateDraftResponse":{"properties":{"id":{"type":"integer","title":"Id"},"conversation_id":{"type":"integer","title":"Conversation Id"},"content_type":{"type":"string","title":"Content Type","description":"'text' | 'image' | 'product'."},"text_content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text Content"},"payload":{"additionalProperties":true,"type":"object","title":"Payload"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"created_by_api_key_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Created By Api Key Id"}},"type":"object","required":["id","conversation_id","content_type"],"title":"CreateDraftResponse","description":"201-create response — same shape as a single draft row."},"CreatorConversationItem":{"properties":{"creator_handle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Creator Handle","description":"Creator username (no leading @)."},"last_message_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Message Text"},"last_message_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Message Time"},"last_message_sender":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Message Sender","description":"'creator' or 'shop'."},"unread_count":{"type":"integer","title":"Unread Count","default":0}},"type":"object","title":"CreatorConversationItem"},"CreatorConversationsResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/CreatorConversationItem"},"type":"array","title":"Data"},"offset":{"type":"integer","title":"Offset"},"limit":{"type":"integer","title":"Limit"},"has_more":{"type":"boolean","title":"Has More"},"total_count":{"type":"integer","title":"Total Count"}},"type":"object","required":["data","offset","limit","has_more","total_count"],"title":"CreatorConversationsResponse"},"CreatorDetailResponse":{"properties":{"data":{"$ref":"#/components/schemas/CreatorListItem"},"groups":{"items":{"$ref":"#/components/schemas/CreatorGroupItem"},"type":"array","title":"Groups","default":[]},"products":{"items":{"$ref":"#/components/schemas/CreatorProductItem"},"type":"array","title":"Products","default":[]},"videos":{"items":{"$ref":"#/components/schemas/VideoItem"},"type":"array","title":"Videos","default":[]},"currency":{"type":"string","title":"Currency"}},"type":"object","required":["data","currency"],"title":"CreatorDetailResponse"},"CreatorFilters":{"properties":{"product_categories":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":20},{"type":"null"}],"title":"Product Categories","description":"TikTok Shop product category names (e.g. 'Beauty & Personal Care')."},"follower_count":{"anyOf":[{"$ref":"#/components/schemas/FilterRange"},{"type":"null"}]},"creator_ethnicity":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":10},{"type":"null"}],"title":"Creator Ethnicity"},"gender":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":4},{"type":"null"}],"title":"Gender"},"age_range":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":10},{"type":"null"}],"title":"Age Range","description":"Age buckets, e.g. ['18-24', '25-34']. Buckets are platform-defined."},"languages":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":20},{"type":"null"}],"title":"Languages"},"gmv":{"anyOf":[{"$ref":"#/components/schemas/FilterRange"},{"type":"null"}],"description":"Creator GMV in USD."},"units_sold":{"anyOf":[{"$ref":"#/components/schemas/FilterRange"},{"type":"null"}]},"average_views":{"anyOf":[{"$ref":"#/components/schemas/FilterRange"},{"type":"null"}]},"engagement_rate":{"anyOf":[{"$ref":"#/components/schemas/FilterRange"},{"type":"null"}],"description":"0.0-1.0 fraction (e.g. 0.05 = 5%)."},"post_rate":{"anyOf":[{"$ref":"#/components/schemas/FilterRange"},{"type":"null"}]},"video_gpm":{"anyOf":[{"$ref":"#/components/schemas/FilterRange"},{"type":"null"}]},"live_gpm":{"anyOf":[{"$ref":"#/components/schemas/FilterRange"},{"type":"null"}]},"follower_age":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":10},{"type":"null"}],"title":"Follower Age"},"follower_gender":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":4},{"type":"null"}],"title":"Follower Gender"},"face_visibility":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":10},{"type":"null"}],"title":"Face Visibility"},"content_style":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":10},{"type":"null"}],"title":"Content Style"},"production_quality":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":10},{"type":"null"}],"title":"Production Quality"},"personality_tone":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":10},{"type":"null"}],"title":"Personality Tone"},"body_type":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":10},{"type":"null"}],"title":"Body Type"},"hair_type":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":10},{"type":"null"}],"title":"Hair Type"}},"additionalProperties":false,"type":"object","title":"CreatorFilters","description":"Dynamic creator selection by attribute. When `is_evergreen=true` on\nthe parent automation, the filter is re-evaluated on every run.\n\nMirrors the portal's \"Filters\" tab (formDataTransformer.ts:735-824\n`buildFilters` for the legacy `config.Filters` shape, and\n`buildAISearchFilters` for the AI-search variant). The translator\nsplits AI-only fields out into `config.ai_search_filters` and sets\n`config.uses_ai_filters=true` accordingly.\n\nAll fields are optional — set only the ones you care about. An empty\n`CreatorFilters({})` is invalid (use `lists` mode instead)."},"CreatorGroupItem":{"properties":{"group_id":{"type":"string","title":"Group Id"},"group_name":{"type":"string","title":"Group Name"}},"type":"object","required":["group_id","group_name"],"title":"CreatorGroupItem"},"CreatorListItem":{"properties":{"creator_handle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Creator Handle"},"follower_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Follower Count","description":"DB: follower_num"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Display status from rev_status_mapping()"},"shop_gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Shop Gmv","description":"Lifetime shop-specific GMV. DB: crm_creators.gmv"},"overall_gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Overall Gmv","description":"Overall GMV from regional creator table. DB: Creator.gmv_num"},"overall_fulfillment_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Overall Fulfillment Rate","description":"Overall fulfillment rate from regional creator table"},"shop_units_sold":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Shop Units Sold","description":"DB: item_sold"},"shop_video_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Shop Video Count","description":"DB: video_num"},"video_views":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Video Views","description":"DB: content_video_views"},"sample_received":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sample Received","description":"DB: sample_received_num"},"commission_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Commission Rate"},"est_commission":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Est Commission","description":"DB: estimate_commission"},"fulfillment_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fulfillment Rate","description":"Shop-specific fulfillment rate from crm_creators"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"},"product_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Id"},"product_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Title"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"}},"type":"object","title":"CreatorListItem"},"CreatorMessageDraft":{"properties":{"id":{"type":"integer","title":"Id"},"conversation_id":{"type":"integer","title":"Conversation Id"},"content_type":{"type":"string","title":"Content Type","description":"'text' | 'image' | 'product'."},"text_content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text Content"},"payload":{"additionalProperties":true,"type":"object","title":"Payload"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"created_by_api_key_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Created By Api Key Id"}},"type":"object","required":["id","conversation_id","content_type"],"title":"CreatorMessageDraft","description":"One draft row, as returned by create / list / delete echo paths.\n\nMirrors the column shape in ``conversation_message_drafts``. Only the\nfield matching `content_type` is populated — for `text` drafts,\n`text_content` is set and `payload` is `{}`; for `image` drafts\n`payload = {\"image_url\": \"...\"}`; for `product` drafts\n`payload = {\"product_id\": \"...\"}`. Keeping the response shape tight\nmeans an agent caller can render the draft without parsing more than\nnecessary."},"CreatorMessageItem":{"properties":{"id":{"type":"integer","title":"Id"},"sender":{"type":"string","title":"Sender","description":"'creator' (inbound) or 'shop' (outbound)."},"content_type":{"type":"string","title":"Content Type"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text"},"sent_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sent At"},"image":{"anyOf":[{"$ref":"#/components/schemas/ImageContent"},{"type":"null"}]},"product_card":{"anyOf":[{"$ref":"#/components/schemas/ProductCardContent"},{"type":"null"}]},"target_collaboration_card":{"anyOf":[{"$ref":"#/components/schemas/TargetCollaborationCardContent"},{"type":"null"}]},"free_sample_card":{"anyOf":[{"$ref":"#/components/schemas/FreeSampleCardContent"},{"type":"null"}]},"crm_text_with_image_card":{"anyOf":[{"$ref":"#/components/schemas/CrmTextWithImageCardContent"},{"type":"null"}]},"crm_text_with_products_card":{"anyOf":[{"$ref":"#/components/schemas/CrmTextWithProductsCardContent"},{"type":"null"}]}},"type":"object","required":["id","sender","content_type"],"title":"CreatorMessageItem","description":"A single creator-conversation message.\n\nThe `text` field is populated for plain-text messages. For richer types\n(image, product card, target-collab card, free-sample card, CRM cards)\nthe matching nested object below carries the type-specific payload —\nonly the one matching `content_type` is populated; the rest are null.\nAI agents need these payloads to understand what was actually sent\n(image URL, product_id, invitation_group_id, etc.); without them a\nnon-text content_type leaves the caller blind."},"CreatorMessagesResponse":{"properties":{"creator_handle":{"type":"string","title":"Creator Handle"},"data":{"items":{"$ref":"#/components/schemas/CreatorMessageItem"},"type":"array","title":"Data"},"page":{"type":"integer","title":"Page"},"has_more":{"type":"boolean","title":"Has More"}},"type":"object","required":["creator_handle","data","page","has_more"],"title":"CreatorMessagesResponse"},"CreatorPerformanceItem":{"properties":{"creator_id":{"type":"string","title":"Creator Id"},"creator_handle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Creator Handle"},"gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gmv","description":"GMV earned in the date range (not lifetime)"},"units_sold":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Units Sold"},"order_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Order Count"},"est_commission":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Est Commission"},"follower_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Follower Count","description":"Latest follower count in the period"}},"type":"object","required":["creator_id"],"title":"CreatorPerformanceItem","description":"Creator with period-specific metrics from creator_info (daily table)."},"CreatorPerformanceRequest":{"properties":{"page":{"type":"integer","minimum":1.0,"title":"Page","default":1},"page_size":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Page Size","default":50},"sort_by":{"anyOf":[{"type":"string","enum":["gmv","units_sold","order_count","est_commission","follower_count"]},{"type":"null"}],"title":"Sort By"},"sort_dir":{"type":"string","enum":["asc","desc"],"title":"Sort Dir","default":"desc"},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"creator_handle":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Creator Handle"},"min_gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Gmv"}},"additionalProperties":false,"type":"object","title":"CreatorPerformanceRequest","description":"POST /creators/performance request body."},"CreatorPerformanceResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/CreatorPerformanceItem"},"type":"array","title":"Data"},"currency":{"type":"string","title":"Currency"},"pagination":{"title":"Pagination"},"date_range":{"anyOf":[{"$ref":"#/components/schemas/DateRange"},{"type":"null"}]},"shops_queried":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Shops Queried","description":"Shop names included when querying multiple shops (x-shop-id: all or comma-separated)."}},"type":"object","required":["data","currency","pagination"],"title":"CreatorPerformanceResponse"},"CreatorProductItem":{"properties":{"product_id":{"type":"string","title":"Product Id"},"product_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Title"},"commission_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Commission Rate"},"shop_units_sold":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Shop Units Sold"},"shop_gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Shop Gmv"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},"type":"object","required":["product_id"],"title":"CreatorProductItem"},"CreatorProductsPaginatedResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/CreatorProductItem"},"type":"array","title":"Data"},"currency":{"type":"string","title":"Currency"},"pagination":{"title":"Pagination"},"shops_queried":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Shops Queried","description":"Shop names included when querying multiple shops (x-shop-id: all or comma-separated)."}},"type":"object","required":["data","currency","pagination"],"title":"CreatorProductsPaginatedResponse"},"CreatorProductsRequest":{"properties":{"page":{"type":"integer","minimum":1.0,"title":"Page","default":1},"page_size":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Page Size","default":50}},"additionalProperties":false,"type":"object","title":"CreatorProductsRequest","description":"POST /creators/{creator_id}/products request body."},"CreatorReplyDryRunResponse":{"properties":{"dry_run":{"type":"boolean","title":"Dry Run","default":true},"would_send_to":{"type":"string","title":"Would Send To"},"message":{"type":"string","title":"Message"}},"type":"object","required":["would_send_to","message"],"title":"CreatorReplyDryRunResponse","description":"Shape returned for `X-Dry-Run: true` (HTTP 200). Distinct from the\n201 send response — nothing is sent, so there is no message id."},"CreatorReplyImageDryRunResponse":{"properties":{"dry_run":{"type":"boolean","title":"Dry Run","default":true},"would_send_to":{"type":"string","title":"Would Send To"},"image_url":{"type":"string","title":"Image Url"}},"type":"object","required":["would_send_to","image_url"],"title":"CreatorReplyImageDryRunResponse"},"CreatorReplyImageInfo":{"properties":{"url":{"type":"string","title":"Url"},"width":{"type":"integer","title":"Width","default":0},"height":{"type":"integer","title":"Height","default":0}},"type":"object","required":["url"],"title":"CreatorReplyImageInfo"},"CreatorReplyImageRequest":{"properties":{"image_url":{"type":"string","maxLength":2000,"minLength":10,"title":"Image Url","description":"HTTPS URL of an image to forward to the creator. We fetch it server-side (must resolve to a public IP, return Content-Type image/*, ≤ 5 MB), then upload to TikTok and dispatch."}},"type":"object","required":["image_url"],"title":"CreatorReplyImageRequest"},"CreatorReplyImageResponse":{"properties":{"conversation_id":{"type":"integer","title":"Conversation Id"},"message_id":{"type":"integer","title":"Message Id"},"message_index":{"type":"integer","title":"Message Index"},"sent_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sent At"},"image":{"$ref":"#/components/schemas/CreatorReplyImageInfo"},"dry_run":{"type":"boolean","title":"Dry Run","default":false}},"type":"object","required":["conversation_id","message_id","message_index","image"],"title":"CreatorReplyImageResponse"},"CreatorReplyProductDryRunResponse":{"properties":{"dry_run":{"type":"boolean","title":"Dry Run","default":true},"would_send_to":{"type":"string","title":"Would Send To"},"product_id":{"type":"string","title":"Product Id"}},"type":"object","required":["would_send_to","product_id"],"title":"CreatorReplyProductDryRunResponse"},"CreatorReplyProductRequest":{"properties":{"product_id":{"type":"string","maxLength":64,"minLength":1,"title":"Product Id","description":"A TikTok product_id from THIS shop's catalog. Sending another shop's product_id is rejected with 400."}},"type":"object","required":["product_id"],"title":"CreatorReplyProductRequest"},"CreatorReplyProductResponse":{"properties":{"conversation_id":{"type":"integer","title":"Conversation Id"},"message_id":{"type":"integer","title":"Message Id"},"message_index":{"type":"integer","title":"Message Index"},"sent_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sent At"},"product_id":{"type":"string","title":"Product Id"},"dry_run":{"type":"boolean","title":"Dry Run","default":false}},"type":"object","required":["conversation_id","message_id","message_index","product_id"],"title":"CreatorReplyProductResponse"},"CreatorReplyRequest":{"properties":{"message":{"type":"string","maxLength":4000,"minLength":1,"title":"Message","description":"Plain-text reply body to send to the creator."}},"type":"object","required":["message"],"title":"CreatorReplyRequest"},"CreatorReplyResponse":{"properties":{"conversation_id":{"type":"integer","title":"Conversation Id"},"message_id":{"type":"integer","title":"Message Id"},"message_index":{"type":"integer","title":"Message Index"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text"},"sent_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sent At"},"dry_run":{"type":"boolean","title":"Dry Run","default":false}},"type":"object","required":["conversation_id","message_id","message_index"],"title":"CreatorReplyResponse"},"CreatorSource":{"type":"string","enum":["my_community","social_army"],"title":"CreatorSource","description":"How creators are sourced for a campaign."},"CreatorStateChangeResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"code":{"type":"string","title":"Code","default":"ok"},"data":{"additionalProperties":true,"type":"object","title":"Data"}},"type":"object","required":["success"],"title":"CreatorStateChangeResponse","description":"Response shape shared by accept / reject / reactivate."},"CreatorStatus":{"type":"string","enum":["pending","invited","applied","active","rejected","Paid/Settled","archived","completed","removed"],"title":"CreatorStatus","description":"Lifecycle status of a creator within a campaign."},"CreatorTrendSeries":{"properties":{"affiliate_gmv":{"items":{"type":"number"},"type":"array","title":"Affiliate Gmv"},"video_gmv":{"items":{"type":"number"},"type":"array","title":"Video Gmv"},"videos_posted":{"items":{"type":"integer"},"type":"array","title":"Videos Posted"},"total_video_views":{"items":{"type":"integer"},"type":"array","title":"Total Video Views"},"units_sold":{"items":{"type":"integer"},"type":"array","title":"Units Sold"},"commission":{"items":{"type":"number"},"type":"array","title":"Commission"}},"type":"object","required":["affiliate_gmv","video_gmv","videos_posted","total_video_views","units_sold","commission"],"title":"CreatorTrendSeries","description":"Six parallel daily series. Every array has one entry per day in the\nwindow, ordered oldest → newest, aligned positionally with `days`."},"CreatorTrendsRequest":{"properties":{"creator_handles":{"items":{"type":"string"},"type":"array","maxItems":100,"minItems":1,"title":"Creator Handles","description":"Creator handles to fetch series for. Max 100 per call — this mirrors the internal per-page cap and keeps the response bounded. Handles that don't resolve in this shop are reported in `unresolved_handles`."},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date","description":"Window start (inclusive). Omit for a trailing 28-day window ending at end_date."},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date","description":"Window end (inclusive). Defaults to today."}},"additionalProperties":false,"type":"object","required":["creator_handles"],"title":"CreatorTrendsRequest","description":"POST /creators/trends request body."},"CreatorTrendsResponse":{"properties":{"data":{"additionalProperties":{"$ref":"#/components/schemas/CreatorTrendSeries"},"type":"object","title":"Data","description":"Keyed by creator handle. Creators with no activity are zero-filled, not omitted."},"days":{"items":{"type":"string"},"type":"array","title":"Days","description":"ISO date axis for every series in `data`."},"unresolved_handles":{"items":{"type":"string"},"type":"array","title":"Unresolved Handles","description":"Requested handles with no creator record in this shop. Absent from `data`."},"currency":{"type":"string","title":"Currency"},"date_range":{"$ref":"#/components/schemas/ResolvedDateRange"}},"type":"object","required":["data","days","currency","date_range"],"title":"CreatorTrendsResponse"},"CreatorVideosRequest":{"properties":{"page":{"type":"integer","minimum":1.0,"title":"Page","default":1},"page_size":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Page Size","default":50},"sort_by":{"anyOf":[{"type":"string","enum":["video_gmv","views","units_sold","posted_date","like_count"]},{"type":"null"}],"title":"Sort By"},"sort_dir":{"type":"string","enum":["asc","desc"],"title":"Sort Dir","default":"desc"},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"}},"additionalProperties":false,"type":"object","title":"CreatorVideosRequest","description":"POST /creators/{creator_id}/videos request body."},"CreatorsListPaginatedResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/CreatorListItem"},"type":"array","title":"Data"},"currency":{"type":"string","title":"Currency"},"pagination":{"title":"Pagination"},"date_range":{"anyOf":[{"$ref":"#/components/schemas/DateRange"},{"type":"null"}]},"shops_queried":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Shops Queried","description":"Shop names included when querying multiple shops (x-shop-id: all or comma-separated)."}},"type":"object","required":["data","currency","pagination"],"title":"CreatorsListPaginatedResponse"},"CreatorsListRequest":{"properties":{"page":{"type":"integer","minimum":1.0,"title":"Page","default":1},"page_size":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Page Size","default":50},"sort_by":{"anyOf":[{"type":"string","enum":["shop_gmv","follower_count","shop_video_count","shop_units_sold","video_views","updated_at","created_at"]},{"type":"null"}],"title":"Sort By"},"sort_dir":{"type":"string","enum":["asc","desc"],"title":"Sort Dir","default":"desc"},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"creator_handle":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Creator Handle"},"min_shop_gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Shop Gmv"},"min_overall_gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Overall Gmv"},"min_videos":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Videos"},"min_followers":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Followers"},"group_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Group Id"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":10},{"type":"null"}],"title":"Tags"},"product_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Id"}},"additionalProperties":false,"type":"object","title":"CreatorsListRequest","description":"POST /creators/list request body."},"CreatorsSummaryRequest":{"properties":{"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date","description":"Window start (inclusive). Omit for a trailing 28-day window ending at end_date."},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date","description":"Window end (inclusive). Defaults to today."}},"additionalProperties":false,"type":"object","title":"CreatorsSummaryRequest","description":"POST /creators/summary request body."},"CreatorsSummaryResponse":{"properties":{"data":{"$ref":"#/components/schemas/CreatorsSummaryTotals"},"currency":{"type":"string","title":"Currency"},"date_range":{"$ref":"#/components/schemas/ResolvedDateRange"}},"type":"object","required":["data","currency","date_range"],"title":"CreatorsSummaryResponse"},"CreatorsSummaryTotals":{"properties":{"new_creators":{"type":"integer","title":"New Creators","description":"Creators whose first-ever CRM record falls inside the window."},"gmv_generated":{"type":"number","title":"Gmv Generated","description":"Affiliate-attributed GMV earned in the window."},"video_posts":{"type":"integer","title":"Video Posts","description":"Distinct videos posted in the window."},"orders":{"type":"integer","title":"Orders","description":"Affiliate orders placed in the window."}},"type":"object","required":["new_creators","gmv_generated","video_posts","orders"],"title":"CreatorsSummaryTotals"},"CreatorsToExclude":{"properties":{"list_upload":{"items":{"type":"string"},"type":"array","maxItems":100000,"title":"List Upload"},"lists_selected":{"items":{"type":"string"},"type":"array","maxItems":200,"title":"Lists Selected"},"crm_groups":{"items":{"type":"string"},"type":"array","maxItems":20,"title":"Crm Groups","description":"UUIDs of existing CRM Groups whose current members should be excluded. Use `GET /crm-groups` to discover."},"exclude_previously_messaged":{"type":"boolean","title":"Exclude Previously Messaged","description":"Skip creators this shop has DM'd before in any prior automation. Maps to config.exclude_previously_messaged.","default":false}},"additionalProperties":false,"type":"object","title":"CreatorsToExclude","description":"Outreach-paradigm recipient exclusion. Mirrors `creators_to_omit` in\nthe legacy DB column / frontend payload, plus the boolean\n`exclude_previously_messaged` flag from the portal's Exclude Creators panel."},"CreatorsToInclude":{"properties":{"list_upload":{"items":{"type":"string"},"type":"array","maxItems":100000,"title":"List Upload","description":"Ad-hoc creator handles (TikTok @-names) to include in this automation only. NOT persisted as a Lists row."},"lists_selected":{"items":{"type":"string"},"type":"array","maxItems":200,"title":"Lists Selected","description":"IDs of existing Lists (vault_files.resource_id) whose creators should be included. Use `POST /lists` to upload reusable lists."},"filters":{"anyOf":[{"$ref":"#/components/schemas/CreatorFilters"},{"type":"null"}],"description":"Dynamic creator selection by attribute. When set, the parent automation's `is_evergreen=true` flag re-evaluates the filter on every run. Mutually exclusive with `list_upload`/`lists_selected` and `crm_group_id`."},"crm_group_id":{"anyOf":[{"type":"string","maxLength":64,"minLength":1},{"type":"null"}],"title":"Crm Group Id","description":"Single CRM group ID (UUID string). Membership is dynamic — the runtime re-evaluates on every send. Mutually exclusive with the other two modes. Use `GET /crm-groups` to discover available groups (returns the canonical IDs to pass here)."},"crm_group_entered_after":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Crm Group Entered After","description":"Optional CRM-group filter — only include members who entered the group ON or AFTER this date. Ignored unless `crm_group_id` is set."},"crm_group_entered_before":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Crm Group Entered Before","description":"Optional CRM-group filter — only include members who entered the group ON or BEFORE this date. Ignored unless `crm_group_id` is set."}},"additionalProperties":false,"type":"object","title":"CreatorsToInclude","description":"Outreach-paradigm recipient inclusion. Three mutually-exclusive\nmodes (cf. INTERNAL-design-review.md §11A):\n\n  * `lists` mode — `list_upload` and/or `lists_selected` populated\n  * `filters` mode — `filters` populated (dynamic by attribute)\n  * `crm_group` mode — `crm_group_id` populated (saved predicate)\n\nThe validator below rejects any request that mixes modes. The\n`creator_source_type` discriminator written to `config` is derived\nfrom which mode is set — callers don't need to set it explicitly."},"CrmGroupCondition":{"properties":{"field":{"type":"string","title":"Field","description":"e.g. 'status', 'products', 'shop_gmv'."},"operator":{"type":"string","title":"Operator","description":"e.g. 'is_one_of', 'gte'."},"value":{"title":"Value"},"value_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Value Count","description":"Set only when `value` is a long list that has been sampled down. It is the true length; `value` holds the first 50 entries. Groups built from a dashboard segment store their whole membership inline, so this is common on those. Read the group's `creator_count` for its size, not `len(value)`."}},"type":"object","required":["field","operator","value"],"title":"CrmGroupCondition"},"CrmGroupFromSegmentRequest":{"properties":{"group_name":{"type":"string","maxLength":255,"minLength":1,"title":"Group Name","description":"Name for the new group. Creation upserts by (shop, group_name) — reusing a name overwrites that group's definition and re-materializes its members."},"tier":{"anyOf":[{"type":"string","maxLength":8},{"type":"null"}],"title":"Tier","description":"GMV tier code to narrow the segment, e.g. 'L3'. Omit for all tiers."},"automation_id":{"anyOf":[{"type":"integer","minimum":1.0},{"type":"null"}],"title":"Automation Id","description":"Restrict the segment to creators reached by this automation."},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date","description":"Segment window start (inclusive)."},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date","description":"Segment window end (inclusive)."},"group_tags":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":50},{"type":"null"}],"title":"Group Tags","description":"Optional tags to attach to the group."}},"additionalProperties":false,"type":"object","required":["group_name"],"title":"CrmGroupFromSegmentRequest","description":"POST /crm-groups/from-segment request body."},"CrmGroupFromSegmentResponse":{"properties":{"data":{"$ref":"#/components/schemas/CrmGroupFromSegmentResult"},"dry_run":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Dry Run"}},"type":"object","required":["data"],"title":"CrmGroupFromSegmentResponse"},"CrmGroupFromSegmentResult":{"properties":{"crm_group_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Crm Group Id","description":"UUID of the created group. Null on a dry run."},"name":{"type":"string","title":"Name"},"creator_count":{"type":"integer","title":"Creator Count","description":"Creators actually materialized into the group — those that mapped to a CRM record. This is the group's real size."},"handles_resolved":{"type":"integer","title":"Handles Resolved","description":"Creators the segment matched before CRM mapping. Always >= creator_count; the gap between the two is the drop-off from creators that have no CRM record (DM-only and organic creators cannot be grouped)."},"group_tags":{"items":{"type":"string"},"type":"array","title":"Group Tags"}},"type":"object","required":["name","creator_count","handles_resolved"],"title":"CrmGroupFromSegmentResult"},"CrmGroupResource":{"properties":{"crm_group_id":{"type":"string","title":"Crm Group Id","description":"UUID — stable across creator membership changes."},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"conditions":{"items":{"$ref":"#/components/schemas/CrmGroupCondition"},"type":"array","title":"Conditions","description":"Filter conditions joined with AND. Membership semantics depend on how the group was built: filter-built groups are dynamic and creators flow in/out as their attributes change, while groups created from a dashboard segment (POST /crm-groups/from-segment) are snapshots whose members were fixed at creation. A single `creatorNames` condition is the signal for the latter."},"creator_count":{"type":"integer","title":"Creator Count","description":"Current member count."},"last_synced_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Synced At"}},"type":"object","required":["crm_group_id","name","creator_count"],"title":"CrmGroupResource"},"CrmGroupResponse":{"properties":{"data":{"$ref":"#/components/schemas/CrmGroupResource"}},"type":"object","required":["data"],"title":"CrmGroupResponse"},"CrmGroupsPaginatedResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/CrmGroupResource"},"type":"array","title":"Data"},"pagination":{"title":"Pagination"}},"type":"object","required":["data","pagination"],"title":"CrmGroupsPaginatedResponse"},"CrmProductCardItem":{"properties":{"product_id":{"type":"string","title":"Product Id"},"product_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Name"},"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url"}},"type":"object","required":["product_id"],"title":"CrmProductCardItem","description":"Single product within a CRM text with products card."},"CrmTextWithImageCardContent":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"}},"type":"object","title":"CrmTextWithImageCardContent","description":"CRM text with image card content fields."},"CrmTextWithProductsCardContent":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Content"},"products":{"anyOf":[{"items":{"$ref":"#/components/schemas/CrmProductCardItem"},"type":"array"},{"type":"null"}],"title":"Products"}},"type":"object","title":"CrmTextWithProductsCardContent","description":"CRM text with products card content fields."},"DateRange":{"properties":{"start_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"}},"type":"object","title":"DateRange"},"DepositIntentResponse":{"properties":{"session_id":{"type":"string","title":"Session Id","description":"Stripe Checkout session ID."},"redirect_url":{"type":"string","title":"Redirect Url","description":"URL to redirect the user to in order to complete the deposit. No money moves until the user finishes Stripe's hosted checkout."}},"additionalProperties":true,"type":"object","required":["session_id","redirect_url"],"title":"DepositIntentResponse","description":"201 response for ``POST /campaigns/payments/deposit-intent``."},"DmConfig":{"properties":{"spark_code":{"anyOf":[{"$ref":"#/components/schemas/DmSparkCodeConfig"},{"type":"null"}]}},"additionalProperties":false,"type":"object","title":"DmConfig","description":"DM type-specific config blob, populated based on `mode`."},"DmConfigPatchPayload":{"properties":{"spark_code":{"anyOf":[{"$ref":"#/components/schemas/DmSparkCodeConfig"},{"type":"null"}]}},"additionalProperties":false,"type":"object","title":"DmConfigPatchPayload","description":"All-Optional companion to `DmConfig` for PATCH bodies."},"DmPersonalizationConfig":{"properties":{"enabled":{"type":"boolean","title":"Enabled","description":"Master switch. Set false to persist the config without personalizing sends (e.g. staging copy for later).","default":true},"tone":{"anyOf":[{"type":"string","enum":["engaging","bold","inspirational"]},{"type":"null"}],"title":"Tone","description":"AI rewrite tone. Null uses the engine default (engaging)."},"fallback_message":{"type":"string","maxLength":4000,"minLength":1,"title":"Fallback Message","description":"Sent when a creator has too little signal to personalize. Required — personalization must always have a safe fallback."},"offer_discussion":{"type":"string","maxLength":2000,"title":"Offer Discussion","description":"[Offer Discussion] block value — your offer text.","default":""},"product_description":{"type":"string","maxLength":4000,"title":"Product Description","description":"[Product Description] block value. Pre-resolve via POST /personalization/product-blocks.","default":""},"product_selling_points":{"type":"string","maxLength":2000,"title":"Product Selling Points","description":"[Product Selling Points] block value. Pre-resolve via POST /personalization/product-blocks.","default":""}},"additionalProperties":false,"type":"object","required":["fallback_message"],"title":"DmPersonalizationConfig","description":"CORE-4857 — enable AI personalization of the initial-outreach DM.\n\nWhen set on ``POST /automations/dm``, the send engine rewrites the DM body\nper creator using the [Block] tokens in your message text: Content\nDiscussion and Product Discussion are AI-generated from the creator's own\nsignals, while Product Description / Selling Points / Offer are the fixed\nstrings you provide here. A creator with too little signal to personalize\nreceives ``fallback_message`` instead.\n\nAll copy fields are stored verbatim — there is NO server-side AI at create\ntime — so the create is deterministic and idempotent, and the send path's\nreuse hash stays stable. Resolve the product blocks beforehand via\n``POST /personalization/product-blocks`` and preview any creator via\n``POST /personalization/preview`` so what ships is what you approved."},"DmSparkCodeConfig":{"properties":{"only_collect_creator_information":{"type":"boolean","title":"Only Collect Creator Information","default":false},"spark_code_submission_url":{"type":"string","maxLength":500,"title":"Spark Code Submission Url","description":"Portal-hosted form URL. Auto-generated if not provided by the route."}},"additionalProperties":false,"type":"object","required":["spark_code_submission_url"],"title":"DmSparkCodeConfig","description":"dm-mode=spark_code — TikTok spark code collection form."},"DraftListResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/CreatorMessageDraft"},"type":"array","title":"Data"},"page":{"type":"integer","title":"Page"},"page_size":{"type":"integer","title":"Page Size"},"total":{"type":"integer","title":"Total"},"has_more":{"type":"boolean","title":"Has More"}},"type":"object","required":["data","page","page_size","total","has_more"],"title":"DraftListResponse","description":"Paginated list. Mirrors the public-API pagination shape from\n`/automations/list` and friends: `data` array + pagination meta."},"EmailAttachment":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name","description":"Display name for the attachment (e.g. `deck.pdf`)."},"url":{"type":"string","maxLength":2048,"minLength":1,"title":"Url","description":"Hosted HTTPS URL of the attachment. Reacher fetches the file from this URL at create time, runs the standard upload validation pipeline (size, MIME type, virus scan), and re-hosts to internal storage. The provided URL is NOT used at send time — only at create time."}},"additionalProperties":false,"type":"object","required":["name","url"],"title":"EmailAttachment","description":"Email automation attachment. Per architecture review 2026-05-01:\nthe API NEVER accepts file uploads — callers provide a hosted HTTPS\nURL and the server fetches + re-hosts to internal storage."},"EmailFilters":{"properties":{"min_creator_gmv":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Min Creator Gmv"},"min_followers":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Min Followers"},"min_engagement_rate":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Min Engagement Rate"},"min_avg_views":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Min Avg Views"},"regions":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":20},{"type":"null"}],"title":"Regions"},"categories":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":20},{"type":"null"}],"title":"Categories"}},"additionalProperties":true,"type":"object","title":"EmailFilters","description":"selection_mode=filters — recipients via creator search filters.\nSubset of the saved_searches.filters JSONB shape — fields that make sense\nfor email outreach. Backend translates this into the legacy filters JSON."},"EmailMailingList":{"properties":{"list_upload_emails":{"items":{"type":"string"},"type":"array","maxItems":50000,"title":"List Upload Emails"},"lists_selected":{"items":{"type":"string"},"type":"array","maxItems":200,"title":"Lists Selected"}},"additionalProperties":false,"type":"object","title":"EmailMailingList","description":"selection_mode=mailing_list — recipients via uploaded emails or\nexisting Lists (vault_files with non-empty `creator_emails`).\n\nNOTE: lists referenced by `lists_selected` MUST contain at least one\nemail address — the `vault_files.creator_emails` JSONB column must be\nnon-empty. Targeting requires emails to send to. The route validates\nthis at create time and returns 400 `LIST_MISSING_EMAILS` for any\nlist that lacks emails. Per architecture review 2026-05-01."},"FilterRange":{"properties":{"min":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Min"},"max":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Max"}},"additionalProperties":false,"type":"object","title":"FilterRange","description":"Inclusive numeric range. At least one of `min` / `max` must be set;\nthe other is open-ended.\n\nUsed by `CreatorFilters` for every range-style filter (GMV, follower\ncount, average views, engagement rate, etc.). API exposes `{min, max}`\nobjects; the translator converts to the legacy `[\"custom:min-max\"]`\nstring form `config.Filters` expects."},"FollowUpStep":{"properties":{"delay_days":{"type":"integer","maximum":30.0,"minimum":0.0,"title":"Delay Days","description":"Days to wait after the prior step (or after initial outreach for the first follow-up)."},"addons":{"items":{"$ref":"#/components/schemas/MessageAddon"},"type":"array","maxItems":5,"minItems":1,"title":"Addons","description":"Addons sent together when this follow-up step fires. Only `message` and `image` addon types are supported in follow-ups; `product_card`, `spark_code_form`, and `target_invite` are initial-outreach-only — use `messages` for those."}},"additionalProperties":false,"type":"object","required":["delay_days","addons"],"title":"FollowUpStep","description":"One delayed follow-up after the initial outreach. Each step has a\ndelay (days after the prior step / initial outreach for the first\nfollow-up) and one or more addons that run together when the step\nfires.\n\nPer architecture review 2026-05-01 the API was refactored from a\nsingle `messages: List[MessageStep]` array (with `step` discriminator)\ninto TWO arrays: `messages` for initial outreach, `follow_ups` for\ndelayed steps. Mirrors the runtime config shape (`config.addons[]`\nvs `config.FOLLOW_UP_STEPS[]`) — the previous translator was\nflattening follow-up addons into `config.addons[]` where the\nruntime never reads them, silently dropping follow-up sends."},"FreeSampleCardContent":{"properties":{"apply_id":{"type":"string","title":"Apply Id"}},"type":"object","required":["apply_id"],"title":"FreeSampleCardContent","description":"Free sample card-specific content fields."},"GmvMaxCampaignItem":{"properties":{"shop_id":{"type":"integer","title":"Shop Id"},"campaign_id":{"type":"string","title":"Campaign Id"},"campaign_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campaign Name"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"shopping_ads_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Shopping Ads Type","description":"GMV Max objective / ads type."},"budget":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Budget"},"roas_bid":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Roas Bid","description":"Target ROAS bid set on the campaign."},"last_synced_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Synced At","description":"ISO 8601 UTC of the most recent TikTok sync."},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency","description":"ISO currency code for the shop's region."}},"type":"object","required":["shop_id","campaign_id"],"title":"GmvMaxCampaignItem"},"GmvMaxCampaignMetricItem":{"properties":{"date":{"type":"string","title":"Date","description":"ISO 8601 date (YYYY-MM-DD) for this metric row."},"spend":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Spend"},"impressions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Impressions"},"clicks":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Clicks"},"orders":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Orders"},"gross_revenue":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gross Revenue"},"cpc":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cpc"},"cpm":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cpm"},"ctr":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ctr"},"roas":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Roas"}},"type":"object","required":["date"],"title":"GmvMaxCampaignMetricItem"},"GmvMaxCampaignMetricsResponse":{"properties":{"campaign_id":{"type":"string","title":"Campaign Id"},"shop_id":{"type":"integer","title":"Shop Id"},"data":{"items":{"$ref":"#/components/schemas/GmvMaxCampaignMetricItem"},"type":"array","title":"Data"},"start_date":{"type":"string","title":"Start Date"},"end_date":{"type":"string","title":"End Date"},"currency":{"type":"string","title":"Currency"}},"type":"object","required":["campaign_id","shop_id","data","start_date","end_date","currency"],"title":"GmvMaxCampaignMetricsResponse"},"GmvMaxCampaignsResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/GmvMaxCampaignItem"},"type":"array","title":"Data"},"shops_queried":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Shops Queried","description":"Shop names included when querying multiple shops (x-shop-id: all or comma-separated)."}},"type":"object","required":["data"],"title":"GmvMaxCampaignsResponse"},"GuardrailRulePublic":{"properties":{"rule_type":{"$ref":"#/components/schemas/GuardrailRuleType","description":"Which limit this guardrail enforces. `total_creative_boost_cost` is the lifetime spend cap and is *required* on every `set_up_creative_boost` automation."},"operator":{"anyOf":[{"$ref":"#/components/schemas/ConditionOperator"},{"type":"null"}],"description":"Comparison operator. Optional — defaults to `<` for spend-cap-style guardrails when omitted."},"value":{"type":"number","title":"Value","description":"Threshold value."}},"additionalProperties":false,"type":"object","required":["rule_type","value"],"title":"GuardrailRulePublic","description":"A safety cap that prevents the automation from spending past a limit."},"GuardrailRuleType":{"type":"string","enum":["total_creative_boost_cost","creative_roi","creative_revenue","campaign_roi","campaign_revenue","campaign_cost","max_budget_daily","cooldown_hours"],"title":"GuardrailRuleType","description":"What a guardrail enforces.\n\n``total_creative_boost_cost`` is the lifetime spend cap and is\n*mandatory* on every ``set_up_creative_boost`` automation (enforced\nby ``AutomationCreateRequest.validate_total_budget_cap``). The\nremaining values are metric-name guardrails that cap a specific\nmetric per evaluation window."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ImageContent":{"properties":{"url":{"type":"string","title":"Url"},"width":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Width"},"height":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Height"}},"type":"object","required":["url"],"title":"ImageContent","description":"Image-specific content fields."},"LeaderboardPricing":{"properties":{"campaign_type":{"type":"string","const":"leaderboard","title":"Campaign Type","default":"leaderboard"},"leaderboard_ranks":{"items":{"$ref":"#/components/schemas/LeaderboardRank"},"type":"array","minItems":1,"title":"Leaderboard Ranks","description":"At least one rank required"},"reward_type":{"$ref":"#/components/schemas/RewardType","description":"Cash or custom reward"},"metric_type":{"$ref":"#/components/schemas/MetricType","description":"Performance metric to evaluate","default":"gmv"},"scoring_mode":{"type":"string","enum":["single","weighted"],"title":"Scoring Mode","description":"Rank by a single metric or by a weighted composite score","default":"single"},"weighted_metrics":{"anyOf":[{"items":{"$ref":"#/components/schemas/WeightedMetricItem"},"type":"array"},{"type":"null"}],"title":"Weighted Metrics","description":"Metric weights (must sum to 100) when scoring_mode is weighted"},"minimum_gmv_required":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Minimum Gmv Required","description":"Minimum GMV threshold (when metric includes gmv)"},"minimum_views_required":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Minimum Views Required","description":"Minimum views threshold (when metric is views)"},"minimum_videos_required":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Minimum Videos Required","description":"Minimum videos floor (gmv_videos gate; ranking stays by GMV)"}},"type":"object","required":["leaderboard_ranks","reward_type"],"title":"LeaderboardPricing","description":"Pricing configuration for leaderboard campaigns.\n\nExtra keys are IGNORED (the codebase default, matching every sibling\npricing class): the brand FE sends UI-only toggles like\n``minimum_gmv_required_enabled`` that are not modelled here. The\nsmuggling protection the materializer needs comes from re-parsing the\nstored template pricing through this model, which drops unknown keys so\nthey never reach a materialized instance — not from ``extra=\"forbid\"``,\nwhich would break the live create/edit path."},"LeaderboardRank":{"properties":{"rank":{"type":"integer","minimum":1.0,"title":"Rank","description":"Rank position (1-indexed)"},"reward":{"anyOf":[{"type":"number"},{"type":"string"}],"title":"Reward","description":"Reward for this rank"}},"type":"object","required":["rank","reward"],"title":"LeaderboardRank","description":"A single rank entry in a leaderboard campaign."},"ListMembersResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListResource"},"creator_handles":{"items":{"type":"string"},"type":"array","title":"Creator Handles"},"creator_emails":{"items":{"type":"string"},"type":"array","title":"Creator Emails"}},"type":"object","required":["data","creator_handles","creator_emails"],"title":"ListMembersResponse","description":"Includes the actual creator_handles + creator_emails. May be a long\npayload; clients should expect tens of thousands of rows for big lists."},"ListResource":{"properties":{"list_id":{"type":"string","title":"List Id","description":"vault_files.resource_id"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"creator_count":{"type":"integer","title":"Creator Count"},"email_count":{"type":"integer","title":"Email Count"},"state":{"type":"string","title":"State","description":"active | archived"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At"}},"type":"object","required":["list_id","name","creator_count","email_count","state"],"title":"ListResource","description":"A Lists row (vault_files). Used across all 4 automation types."},"ListResponse":{"properties":{"data":{"$ref":"#/components/schemas/ListResource"}},"type":"object","required":["data"],"title":"ListResponse"},"ListsPaginatedResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ListResource"},"type":"array","title":"Data"},"pagination":{"title":"Pagination"}},"type":"object","required":["data","pagination"],"title":"ListsPaginatedResponse"},"LiveGMVChallengePricing":{"properties":{"campaign_type":{"type":"string","const":"live_gmv_challenge","title":"Campaign Type","default":"live_gmv_challenge"},"payment_tiers":{"items":{"$ref":"#/components/schemas/LivePaymentTier"},"type":"array","minItems":1,"title":"Payment Tiers","description":"At least one tier required"},"reward_type":{"$ref":"#/components/schemas/RewardType","description":"Cash or custom reward"},"metric_type":{"type":"string","enum":["live_gmv","sessions","sessions_and_live_gmv"],"title":"Metric Type","description":"Live performance metric to evaluate","default":"live_gmv"},"minimum_live_gmv_required":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Minimum Live Gmv Required","description":"Minimum LIVE GMV floor (sessions_and_live_gmv gate)"},"minimum_sessions_required":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Minimum Sessions Required","description":"Minimum sessions floor (sessions_and_live_gmv gate)"},"ranking_priority":{"anyOf":[{"type":"string","enum":["live_gmv","sessions"]},{"type":"null"}],"title":"Ranking Priority","description":"Primary ranking metric for sessions_and_live_gmv contests; the other is a per-tier requirement"}},"type":"object","required":["payment_tiers","reward_type"],"title":"LiveGMVChallengePricing","description":"Pricing configuration for live GMV challenge campaigns."},"LivePaymentTier":{"properties":{"threshold":{"type":"number","minimum":0.0,"title":"Threshold","description":"Metric threshold for this tier"},"compensation":{"anyOf":[{"type":"number"},{"type":"string"}],"title":"Compensation","description":"Payout at this tier"},"videos_required":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Videos Required","description":"Number of videos required for this tier"},"sessions_required":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sessions Required","description":"Number of live sessions required for this tier (sessions_and_live_gmv metric)"}},"type":"object","required":["threshold","compensation"],"title":"LivePaymentTier","description":"A single tier in a live GMV challenge campaign.\n\nExtends PaymentTier with ``sessions_required`` for the combined\nsessions + live GMV gate."},"MessageAddon":{"properties":{"type":{"type":"string","enum":["message","image","product_card","spark_code_form","target_invite"],"title":"Type"},"body":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Body","description":"Message body (REQUIRED for type=message). Supports `{creator_name}` placeholder."},"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url","description":"Hosted HTTPS URL of the image (REQUIRED for type=image). Reacher fetches this URL server-side and re-hosts on the internal CDN — file uploads (multipart) are NOT accepted. Reject if `http://`, `data:`, `blob:`, or scheme-relative."},"product_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Id","description":"Product ID (REQUIRED for type=product_card; OPTIONAL for type=target_invite)."},"submission_url_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Submission Url Slug","description":"Spark code form slug (REQUIRED for type=spark_code_form)."}},"additionalProperties":true,"type":"object","required":["type"],"title":"MessageAddon","description":"One element of a `messages[].addons` array — a single content block\ninside a follow-up step. Polymorphic by `type`.\n\nPer PR #1513 round-3 review (coderabbitai): each `type` has required\nfields validated below. Without this, `{\"type\": \"image\"}` (no\nimage_url) slipped through and was persisted to the config blob,\nleaving the runtime with an addon that advertised a type but had no\npayload to render."},"MetricType":{"type":"string","enum":["gmv","views","videos_posted","gmv_videos","live_gmv","sessions","sessions_and_live_gmv","avg_views_per_post","units_sold"],"title":"MetricType","description":"Performance metric used for evaluation."},"MetricsSummaryRequest":{"properties":{"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"}},"additionalProperties":false,"type":"object","title":"MetricsSummaryRequest","description":"POST /metrics/summary request body."},"MetricsSummaryResponse":{"properties":{"gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gmv","description":"**Affiliate-attributed** GMV for the period — Reacher's view of creator-driven sales. This is NOT the shop's full GMV. For the headline number that matches the customer's TikTok Seller Center dashboard (which includes ads / Smart+ and organic), use ``total_gmv`` or the dedicated ``POST /shop-gmv/*`` endpoints.","default":0},"total_gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Gmv","description":"**Full shop** GMV for the period — includes ads / Smart+ and organic revenue, not just affiliate-attributed sales. Sourced from the TikTok Seller Center scrape. Coverage is rolling out per-shop; shops not yet covered return ``0``. For per-day breakdown by channel (video / live / product_card) and explicit per-shop currency handling, use ``POST /shop-gmv/*`` instead.","default":0},"active_creators":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Active Creators","description":"Distinct creators with activity in the period (not summed daily counts)","default":0},"video_views":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Video Views","description":"Total video views in the period","default":0},"videos_posted":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Videos Posted","description":"New videos posted in the period","default":0},"samples_approved":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Samples Approved","description":"Samples approved in the period","default":0},"gmv_per_video":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gmv Per Video","description":"Avg GMV per video posted in the period. Matches dashboard 'GMV per Video'","default":0},"gmv_per_sample":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gmv Per Sample","description":"Avg GMV per sample. Matches dashboard 'GMV per Sample'","default":0},"sample_requests":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sample Requests","description":"Total sample requests in the period","default":0},"gmv_driving_videos":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gmv Driving Videos","description":"Videos that generated sales in the period","default":0},"new_creators_posting":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"New Creators Posting","description":"First-time creators who posted in the period","default":0},"open_collabs":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Open Collabs","description":"Open collaboration affiliates added","default":0},"accepted_tc_count":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Accepted Tc Count","description":"Accepted target collaborations","default":0},"creators_reached":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Creators Reached","description":"Creators reached via outreach","default":0},"creators_messaged":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Creators Messaged","description":"Creators messaged","default":0},"tc_invites_sent":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Tc Invites Sent","description":"Target collaboration invites sent","default":0},"emails_sent":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Emails Sent","description":"Emails sent","default":0},"dm_responses":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dm Responses","description":"Sum of daily unique DM responses (not deduplicated across the period)","default":0},"reply_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Reply Rate","description":"DM reply rate: (dm_responses / dm_messages_sent) * 100","default":0},"orders":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Orders","description":"Whole-shop orders for the period across all sales channels, from TikTok Seller Center. Shops not yet on the Seller Center daily sync return ``0``.","default":0},"units_sold":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Units Sold","description":"Whole-shop units sold for the period across all sales channels, from TikTok Seller Center. ``0`` for shops not yet covered.","default":0},"aov":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Aov","description":"Average order value for the period (total GMV / total orders over the date range, not an average of daily AOV). ``0`` when the shop has no orders or is not yet covered.","default":0},"ctr":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ctr","description":"Click-through rate as a percent for the period (total clicks / total impressions over the date range). ``0`` when uncovered or no impressions.","default":0},"conversion_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Conversion Rate","description":"Conversion rate as a percent for the period (total orders / total clicks over the date range). ``0`` when uncovered or no clicks.","default":0},"errors":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Errors","description":"Metric keys that failed (null values in response)"},"start_date":{"type":"string","title":"Start Date"},"end_date":{"type":"string","title":"End Date"},"currency":{"type":"string","title":"Currency"},"shops_queried":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Shops Queried","description":"Shop names included when querying multiple shops (x-shop-id: all or comma-separated)."}},"type":"object","required":["start_date","end_date","currency"],"title":"MetricsSummaryResponse","description":"Pre-computed headline scalars matching dashboard KPIs.\n\nAll metrics use the analytics registry (same queries as the dashboard).\nA null value means the metric errored — check _errors for details."},"OutreachCapacityResponse":{"properties":{"shop_id":{"type":"integer","title":"Shop Id"},"daily_creators_used":{"type":"integer","title":"Daily Creators Used","description":"Distinct (creator, automation) outreach sends counted for this shop in the current UTC day. Counts actual sends only — invites that were skipped (e.g. `creator_not_found`) do not count."},"daily_window_resets_at":{"type":"string","title":"Daily Window Resets At","description":"ISO-8601 UTC timestamp of the next daily-window reset (next 00:00 UTC)."},"concurrent_automations_running":{"type":"integer","title":"Concurrent Automations Running","description":"Outreach automations currently in a running/paused-but-live state."},"max_concurrent_automations":{"type":"integer","title":"Max Concurrent Automations","description":"Plan ceiling for concurrent automations."},"daily_limit_throttled":{"type":"boolean","title":"Daily Limit Throttled","description":"True when a live automation is currently paused by the daily outreach limit."},"weekly_limit_throttled":{"type":"boolean","title":"Weekly Limit Throttled","description":"True when a live automation is currently paused by TikTok's weekly outreach limit. When true, daily capacity is moot until the weekly window resets (start of next week)."}},"type":"object","required":["shop_id","daily_creators_used","daily_window_resets_at","concurrent_automations_running","max_concurrent_automations","daily_limit_throttled","weekly_limit_throttled"],"title":"OutreachCapacityResponse","description":"Current outreach send-capacity for a single shop.\n\nTwo independent limits govern outreach (DM + TC) sends:\n\n1. A **per-shop daily throttle** counted in UTC (resets at 00:00 UTC).\n   `daily_creators_used` is the distinct (creator, automation) pairs the\n   shop has reached today across all non-CRM, non-AI-Chatbot outreach\n   automations. The *ceiling* is plan/TikTok-dependent and is not\n   returned as a single number; `daily_limit_throttled` is true when any\n   live automation is currently paused by the daily limit.\n2. **TikTok's weekly outreach limit** (resets at the start of each\n   week, enforced TikTok-side — Reacher cannot read the remaining\n   count). `weekly_limit_throttled` is true when any live automation is\n   currently paused by the weekly limit; if true, retry next week.\n\nThe daily throttle pool is **shared** between the automation engine and\ndirect `POST /target-collabs` invites — both draw from the same per-shop\ndaily figure."},"PaymentTier":{"properties":{"threshold":{"type":"number","minimum":0.0,"title":"Threshold","description":"Metric threshold for this tier"},"compensation":{"anyOf":[{"type":"number"},{"type":"string"}],"title":"Compensation","description":"Payout at this tier"},"videos_required":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Videos Required","description":"Number of videos required for this tier"}},"type":"object","required":["threshold","compensation"],"title":"PaymentTier","description":"A single tier in a challenge campaign's tiered payment structure."},"PersonalizationCreatorSentMessage":{"properties":{"automation_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Automation Id","description":"The automation that sent this message (null for legacy/pre-save rows)."},"message":{"type":"string","title":"Message","description":"The exact personalized copy sent to this creator."},"is_fallback":{"type":"boolean","title":"Is Fallback"},"tone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tone"},"sent_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sent At","description":"ISO 8601 UTC send time."}},"type":"object","required":["message","is_fallback"],"title":"PersonalizationCreatorSentMessage"},"PersonalizationCreatorSentMessagesResponse":{"properties":{"creator_id":{"type":"string","title":"Creator Id"},"count":{"type":"integer","title":"Count","description":"Number of messages returned (after the limit cap)."},"messages":{"items":{"$ref":"#/components/schemas/PersonalizationCreatorSentMessage"},"type":"array","title":"Messages"}},"type":"object","required":["creator_id","count"],"title":"PersonalizationCreatorSentMessagesResponse","description":"The personalized outreach messages one creator was actually sent."},"PersonalizationFallbackStatsResponse":{"properties":{"automation_id":{"type":"integer","title":"Automation Id"},"messages_sent":{"type":"integer","title":"Messages Sent","description":"Personalized outreach messages this automation has sent (lifetime, all runs)."},"fallback_used":{"type":"integer","title":"Fallback Used","description":"How many of those used the brand fallback (creator had too little signal)."},"fallback_rate":{"type":"number","title":"Fallback Rate","description":"fallback_used / messages_sent (0.0 when nothing has been sent)."}},"type":"object","required":["automation_id","messages_sent","fallback_used","fallback_rate"],"title":"PersonalizationFallbackStatsResponse","description":"Send-report stat: how often an automation's personalized sends fell back."},"PersonalizationPreviewCreator":{"properties":{"creator_id":{"type":"string","maxLength":100,"minLength":1,"title":"Creator Id","description":"TikTok creator id (as returned by GET /automations/{id}/creators)."},"creator_handle":{"type":"string","maxLength":100,"minLength":1,"title":"Creator Handle","description":"TikTok handle without the @."},"creator_first_name":{"type":"string","maxLength":100,"title":"Creator First Name","description":"Optional known first name; the engine resolves the real one when omitted.","default":""}},"additionalProperties":false,"type":"object","required":["creator_id","creator_handle"],"title":"PersonalizationPreviewCreator","description":"The single creator to generate a preview for."},"PersonalizationPreviewRequest":{"properties":{"creator":{"$ref":"#/components/schemas/PersonalizationPreviewCreator"},"message_body":{"type":"string","maxLength":4000,"minLength":1,"title":"Message Body","description":"The DM template with [Block] tokens (mirrors the automation's CREATOR_MESSAGE)."},"tone":{"type":"string","enum":["engaging","bold","inspirational"],"title":"Tone","default":"engaging"},"fallback_message":{"type":"string","maxLength":4000,"minLength":1,"title":"Fallback Message","description":"Sent (name/product blocks filled) when the creator has too little signal to personalize. Required — matches the DM config contract: personalization must always have a safe fallback, so a low-signal preview never returns an empty message."},"brand_name":{"type":"string","maxLength":200,"title":"Brand Name","description":"Brand display name used in the copy. Defaults to the shop name when omitted.","default":""},"product_description":{"type":"string","maxLength":4000,"title":"Product Description","description":"Pre-resolved [Product Description]. Get it from POST /personalization/product-blocks.","default":""},"product_selling_points":{"type":"string","maxLength":2000,"title":"Product Selling Points","description":"Pre-resolved [Product Selling Points]. Get it from POST /personalization/product-blocks.","default":""},"offer_discussion":{"type":"string","maxLength":2000,"title":"Offer Discussion","description":"[Offer Discussion] block value — the brand's offer text.","default":""}},"additionalProperties":false,"type":"object","required":["creator","message_body","fallback_message"],"title":"PersonalizationPreviewRequest","description":"POST /personalization/preview request body.\n\nGenerates the AI personalization blocks for one creator and returns the\nassembled message (or the brand fallback when the creator has too little\nsignal). Product blocks and the offer are passed in pre-resolved, so what\nyou preview is exactly what a send with the same config would ship."},"PersonalizationPreviewResponse":{"properties":{"creator_id":{"type":"string","title":"Creator Id"},"message":{"type":"string","title":"Message","description":"The fully assembled message this creator would receive."},"is_fallback":{"type":"boolean","title":"Is Fallback","description":"True when the creator had too little signal to personalize, so the brand fallback message was used instead of AI-generated blocks."},"blocks_used":{"items":{"type":"string"},"type":"array","title":"Blocks Used","description":"Which [Block] tokens were filled (e.g. content_discussion, product_discussion, product_description, offer_discussion)."}},"type":"object","required":["creator_id","message","is_fallback"],"title":"PersonalizationPreviewResponse","description":"The message a single creator would receive if this automation sent now."},"PersonalizationProductBlocksRequest":{"properties":{"product_id":{"type":"string","maxLength":100,"minLength":1,"title":"Product Id","description":"A product id in this shop's catalog. Blocks resolve empty if it isn't."}},"additionalProperties":false,"type":"object","required":["product_id"],"title":"PersonalizationProductBlocksRequest","description":"POST /personalization/product-blocks request body."},"PersonalizationProductBlocksResponse":{"properties":{"product_description":{"type":"string","title":"Product Description","description":"[Product Description] resolved from this shop's product catalog. Empty when the product isn't in this shop or has no description.","default":""},"product_selling_points":{"type":"string","title":"Product Selling Points","description":"[Product Selling Points] derived from the product's real title/description/attributes. Empty when there's too little grounded product info to derive them.","default":""}},"type":"object","title":"PersonalizationProductBlocksResponse","description":"Product-sourced block values for the personalization composer."},"PersonalizationRetoneRequest":{"properties":{"message_body":{"type":"string","maxLength":4000,"minLength":1,"title":"Message Body","description":"The message body to rewrite. Every [Block] token is preserved."},"tone":{"type":"string","enum":["engaging","bold","inspirational"],"title":"Tone","default":"engaging"}},"additionalProperties":false,"type":"object","required":["message_body"],"title":"PersonalizationRetoneRequest","description":"POST /personalization/retone request body."},"PersonalizationRetoneResponse":{"properties":{"message":{"type":"string","title":"Message","description":"The rewritten message body, with every [Block] token preserved."}},"type":"object","required":["message"],"title":"PersonalizationRetoneResponse","description":"A message body rewritten in a new tone, [Block] tokens preserved."},"ProductCardContent":{"properties":{"product_id":{"type":"string","title":"Product Id"},"product_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Name"},"image_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Image Url"}},"type":"object","required":["product_id"],"title":"ProductCardContent","description":"Product card-specific content fields."},"ProductCreatorItem":{"properties":{"creator_handle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Creator Handle","description":"DB: crm_creators.creator_name"},"follower_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Follower Count","description":"DB: follower_num"},"commission_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Commission Rate"},"gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gmv"}},"type":"object","title":"ProductCreatorItem"},"ProductCreatorsRequest":{"properties":{"page":{"type":"integer","minimum":1.0,"title":"Page","default":1},"page_size":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Page Size","default":50}},"additionalProperties":false,"type":"object","title":"ProductCreatorsRequest","description":"POST /products/{product_id}/creators request body."},"ProductCreatorsResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ProductCreatorItem"},"type":"array","title":"Data"},"currency":{"type":"string","title":"Currency","description":"ISO 4217 currency code for monetary values"},"pagination":{"title":"Pagination"},"shops_queried":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Shops Queried","description":"Shop names included when querying multiple shops (x-shop-id: all or comma-separated)."}},"type":"object","required":["data","currency","pagination"],"title":"ProductCreatorsResponse"},"ProductItem":{"properties":{"product_id":{"type":"string","title":"Product Id"},"tap_link":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tap Link"},"product_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Name"}},"type":"object","required":["product_id"],"title":"ProductItem","description":"A single product in a campaign's product list."},"ProductListItem":{"properties":{"product_id":{"type":"string","title":"Product Id"},"product_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Name"},"gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gmv","description":"SUM of product_info.gmv_amount, normalized to float"},"units_sold":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Units Sold","description":"SUM of item_sold_cnt"},"refund_units":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Refund Units","description":"SUM of refund_item_cnt"},"video_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Video Count","description":"SUM of video_cnt"},"sample_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sample Count","description":"SUM of sample_cnt"},"live_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Live Count","description":"SUM of live_cnt"},"est_commission":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Est Commission","description":"SUM of est_commission_amount, normalized"},"product_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Status","description":"From product_catalog. NULL for unofficial shops."},"sc_total_gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sc Total Gmv","description":"Total product GMV across all sales channels, from TikTok Seller Center."},"sc_orders":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sc Orders","description":"Number of orders for this product, from TikTok Seller Center."},"sc_units_sold":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sc Units Sold","description":"Units sold for this product, from TikTok Seller Center."},"sc_customers":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sc Customers","description":"Distinct buyers for this product, from TikTok Seller Center."},"sc_aov":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sc Aov","description":"Average order value (total GMV / total orders over the date range); null if no orders."},"sc_impressions":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sc Impressions","description":"Product impressions, from TikTok Seller Center."},"sc_clicks":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sc Clicks","description":"Product clicks, from TikTok Seller Center."},"sc_ctr":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sc Ctr","description":"Click-through rate as a percent (total clicks / total impressions over the date range); null if no impressions."},"sc_conversion":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sc Conversion","description":"Conversion rate as a percent (total orders / total clicks over the date range); null if no clicks."},"sc_gmv_per_1k_impr":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sc Gmv Per 1K Impr","description":"GMV per 1,000 impressions (total GMV / total impressions x 1,000 over the date range); null if no impressions."},"sc_add_to_cart":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sc Add To Cart","description":"Add-to-cart count for this product, from TikTok Seller Center."},"sc_affiliate_gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sc Affiliate Gmv","description":"GMV from the affiliate channel."},"sc_seller_video_gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sc Seller Video Gmv","description":"GMV from the seller's own videos."},"sc_seller_live_gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sc Seller Live Gmv","description":"GMV from the seller's own live streams."},"sc_product_card_gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sc Product Card Gmv","description":"GMV from product cards."},"sc_shop_tab_gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sc Shop Tab Gmv","description":"GMV from the shop tab."},"sc_refunds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sc Refunds","description":"Refund amount for this product, from TikTok Seller Center."}},"type":"object","required":["product_id"],"title":"ProductListItem"},"ProductVideosRequest":{"properties":{"page":{"type":"integer","minimum":1.0,"title":"Page","default":1},"page_size":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Page Size","default":50},"sort_by":{"anyOf":[{"type":"string","enum":["video_gmv","views","units_sold","posted_date","like_count"]},{"type":"null"}],"title":"Sort By"},"sort_dir":{"type":"string","enum":["asc","desc"],"title":"Sort Dir","default":"desc"},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"}},"additionalProperties":false,"type":"object","title":"ProductVideosRequest","description":"POST /products/{product_id}/videos request body."},"ProductsListPaginatedResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ProductListItem"},"type":"array","title":"Data"},"currency":{"type":"string","title":"Currency"},"pagination":{"title":"Pagination"},"date_range":{"anyOf":[{"$ref":"#/components/schemas/DateRange"},{"type":"null"}]},"shops_queried":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Shops Queried","description":"Shop names included when querying multiple shops (x-shop-id: all or comma-separated)."}},"type":"object","required":["data","currency","pagination"],"title":"ProductsListPaginatedResponse"},"ProductsListRequest":{"properties":{"page":{"type":"integer","minimum":1.0,"title":"Page","default":1},"page_size":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Page Size","default":50},"sort_by":{"anyOf":[{"type":"string","enum":["gmv","units_sold","video_count","sample_count","est_commission"]},{"type":"null"}],"title":"Sort By"},"sort_dir":{"type":"string","enum":["asc","desc"],"title":"Sort Dir","default":"desc"},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"product_name":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Product Name"}},"additionalProperties":false,"type":"object","title":"ProductsListRequest","description":"POST /products/list request body."},"RejectCreatorRequest":{"properties":{"message":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Message","description":"Optional custom message included in the creator's rejection email/SMS."}},"additionalProperties":false,"type":"object","title":"RejectCreatorRequest","description":"``POST /public/v1/campaigns/{campaign_id}/creators/{creator_id}/reject``."},"RemoveCreatorRequest":{"properties":{"campaign_id":{"type":"integer","exclusiveMinimum":0.0,"title":"Campaign Id","description":"Campaign (in the x-shop-id shop) to remove the creator from."},"message":{"anyOf":[{"type":"string","maxLength":320},{"type":"null"}],"title":"Message","description":"Optional SMS sent to the creator on removal. Removal is otherwise silent (no email, no default SMS). Length capped at 320 to match the service's SMS limit. SKIPPED on X-Dry-Run."}},"additionalProperties":false,"type":"object","required":["campaign_id"],"title":"RemoveCreatorRequest","description":"``POST /public/v1/campaigns/creators/{creator_id}/remove``.\n\nRemoves the creator from ONE campaign (terminal ``removed`` status).\nThe campaign's shop is the request's ``x-shop-id``; the campaign id\nis the only required body field. Paid/completed creators are blocked\n(409) to preserve their payout ledger."},"RemoveCreatorResponse":{"properties":{"success":{"type":"boolean","title":"Success","default":true},"code":{"type":"string","title":"Code","description":"`ok` on a real removal; `already_removed` on an idempotent no-op replay of an already-removed mapping.","default":"ok"},"data":{"additionalProperties":true,"type":"object","title":"Data","description":"`{campaign_creator_mapping_id, status='removed'}`."}},"type":"object","title":"RemoveCreatorResponse","description":"200 response for ``POST /campaigns/creators/{creator_id}/remove``.\n\nMirrors the service envelope. ``code`` is ``\"ok\"`` on a real removal\nand ``\"already_removed\"`` when the call was a no-op idempotent replay\nof an already-``removed`` mapping (the service's internal\n\"invalid transition\" 409 is translated to this 200 — see the route).\n``data`` carries ``campaign_creator_mapping_id`` + ``status`` (always\n``\"removed\"``)."},"RequestMoreContentRequest":{"properties":{"creator_id":{"type":"integer","exclusiveMinimum":0.0,"title":"Creator Id"},"campaign_creator_mapping_id":{"type":"integer","exclusiveMinimum":0.0,"title":"Campaign Creator Mapping Id"},"campaign_id":{"type":"integer","exclusiveMinimum":0.0,"title":"Campaign Id"},"additional_posts":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Additional Posts"},"new_total_payout":{"anyOf":[{"type":"number"},{"type":"string"}],"title":"New Total Payout","description":"New total payout — number for cash campaigns, string label (e.g. 'free product') for custom-reward campaigns."},"minimum_gmv_target":{"type":"number","exclusiveMinimum":0.0,"title":"Minimum Gmv Target"},"tiktok_username":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Tiktok Username"},"creator_status":{"anyOf":[{"$ref":"#/components/schemas/CreatorStatus"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["creator_id","campaign_creator_mapping_id","campaign_id","additional_posts","new_total_payout","minimum_gmv_target"],"title":"RequestMoreContentRequest","description":"``POST /public/v1/campaigns/content/request-more`` — ask a creator\nfor additional posts (fires email + SMS notification).\n\nMirrors ``community.schemas.content_request.RequestMoreContentRequest``\nminus ``shop_id`` (header-injected)."},"RequestMoreContentResponse":{"properties":{"status":{"type":"string","title":"Status","default":"success"}},"additionalProperties":true,"type":"object","title":"RequestMoreContentResponse","description":"200 response for ``POST /campaigns/content/request-more``."},"ResolvedDateRange":{"properties":{"start_date":{"type":"string","title":"Start Date"},"end_date":{"type":"string","title":"End Date"}},"type":"object","required":["start_date","end_date"],"title":"ResolvedDateRange","description":"The window the server actually used, after defaults were applied."},"RetainerPricing":{"properties":{"campaign_type":{"type":"string","const":"retainer","title":"Campaign Type","default":"retainer"},"posts":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Posts","description":"Required posts per creator"},"payment_per_creator":{"anyOf":[{"type":"number"},{"type":"string"}],"title":"Payment Per Creator","description":"Payment amount (float for cash, str for custom)"},"reward_type":{"$ref":"#/components/schemas/RewardType","description":"Cash or custom reward"},"metric_type":{"$ref":"#/components/schemas/MetricType","description":"Performance metric to evaluate","default":"gmv"},"minimum_gmv_required":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Minimum Gmv Required","description":"Minimum GMV threshold"},"minimum_gmv_required_enabled":{"type":"boolean","title":"Minimum Gmv Required Enabled","description":"Whether minimum GMV requirement is active","default":false},"minimum_views_required":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Minimum Views Required","description":"Minimum views threshold"},"minimum_views_required_enabled":{"type":"boolean","title":"Minimum Views Required Enabled","description":"Whether minimum views requirement is active","default":false},"minimum_videos_required":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Minimum Videos Required","description":"Minimum videos threshold (for videos_posted/gmv_videos metric)"}},"type":"object","required":["posts","payment_per_creator","reward_type"],"title":"RetainerPricing","description":"Pricing configuration for retainer campaigns."},"RewardType":{"type":"string","enum":["cash","custom"],"title":"RewardType","description":"Reward payout mechanism."},"SampleByProductItem":{"properties":{"product_id":{"type":"string","title":"Product Id"},"product_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Name"},"total_requests":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Requests"},"approved":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Approved","description":"DB: shipped_sample_cnt"},"sample_gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sample Gmv"},"videos_from_samples":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Videos From Samples","description":"DB: received_sample_and_posted_video_cnt"}},"type":"object","required":["product_id"],"title":"SampleByProductItem"},"SampleListItem":{"properties":{"creator_handle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Creator Handle"},"product_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Id"},"product_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Title"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Display status from rev_status_mapping()"},"gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gmv"},"units_sold":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Units Sold"},"sample_received":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sample Received"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At"},"bio":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bio","description":"Creator bio text. Sourced from the per-region creators table — the same field exposed by the AI Creator Search API. Null when the creator has no regional row."},"categories":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Categories","description":"Creator categories / niches. Parsed via the same helper the AI Creator Search API uses (handles JSON-array and comma-separated stored values). Null when the creator has no regional row."},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Creator contact email when known on the regional creators table. Null when the creator has no regional row or no email captured."}},"type":"object","title":"SampleListItem"},"SampleRequestAutoApprovalSettings":{"properties":{"enabled":{"type":"boolean","title":"Enabled","default":false},"total_weekly_approved_sample_limit":{"anyOf":[{"type":"integer","maximum":100000.0,"minimum":1.0},{"type":"null"}],"title":"Total Weekly Approved Sample Limit"},"always_approve_status":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Always Approve Status","description":"Auto-approve creators whose top-level `automation_status` matches this value (e.g. `APPROVED`, `LIVE_CREATOR`). Null = no status-based bypass."},"always_approve_from_lists_ids":{"items":{"type":"integer"},"type":"array","maxItems":20,"title":"Always Approve From Lists Ids","description":"Vault list IDs whose members bypass criteria gates and are always approved."}},"additionalProperties":false,"type":"object","title":"SampleRequestAutoApprovalSettings","description":"Optional auto-approval gating beyond the min/max criteria.\n\n  * `enabled=False` → manual review for every request that passes the\n    performance gate. Other fields ignored.\n  * `enabled=True` + `always_approve_status` set → auto-approve any\n    creator whose `automation_status` matches; bypass criteria.\n  * `total_weekly_approved_sample_limit` → hard ceiling on weekly\n    auto-approvals across all creators (not per-product).\n  * `always_approve_from_lists_ids` → approve members of these vault\n    lists regardless of criteria.\n\nMirrors `sample_request_config.auto_approval_settings`."},"SampleRequestPatchPayload":{"properties":{"action":{"anyOf":[{"type":"string","enum":["approve","reject"]},{"type":"null"}],"title":"Action"},"rejection_reason":{"anyOf":[{"type":"string","enum":["NOT_MATCH","OFFLINE","OUT_OF_STOCK","OTHER"]},{"type":"null"}],"title":"Rejection Reason"},"approval_message":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Approval Message"},"rejection_message":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Rejection Message"},"message_images":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":5},{"type":"null"}],"title":"Message Images"},"products":{"anyOf":[{"$ref":"#/components/schemas/SampleRequestProducts"},{"type":"null"}]},"minimum_performance_criteria":{"anyOf":[{"$ref":"#/components/schemas/SampleRequestPerformanceCriteria"},{"type":"null"}]},"maximum_performance_criteria":{"anyOf":[{"$ref":"#/components/schemas/SampleRequestPerformanceCriteria"},{"type":"null"}]},"auto_approval_settings":{"anyOf":[{"$ref":"#/components/schemas/SampleRequestAutoApprovalSettings"},{"type":"null"}]},"target_list":{"anyOf":[{"items":{"type":"integer"},"type":"array","maxItems":20},{"type":"null"}],"title":"Target List"}},"additionalProperties":false,"type":"object","title":"SampleRequestPatchPayload","description":"All-Optional companion to `SampleRequestPayload` for PATCH bodies.\nReuses the create-time nested types (`SampleRequestProducts`,\n`SampleRequestPerformanceCriteria`, `SampleRequestAutoApprovalSettings`)\nwhich already have `extra=\"forbid\"`."},"SampleRequestPayload":{"properties":{"action":{"type":"string","enum":["approve","reject"],"title":"Action"},"rejection_reason":{"anyOf":[{"type":"string","enum":["NOT_MATCH","OFFLINE","OUT_OF_STOCK","OTHER"]},{"type":"null"}],"title":"Rejection Reason"},"approval_message":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Approval Message","description":"DM body sent to the creator on approval. Only meaningful when action='approve' — rejected with reject. Persists as `sample_request_config.approval_message`."},"rejection_message":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Rejection Message","description":"DM body sent to the creator on rejection. Only meaningful when action='reject'. Persists as `sample_request_config.rejection_message`."},"message_images":{"items":{"type":"string"},"type":"array","maxItems":5,"title":"Message Images","description":"Optional HTTPS image URLs to attach to the approval / rejection DM. Same URL contract as MessageAddon.image_url — server fetches + re-hosts."},"products":{"$ref":"#/components/schemas/SampleRequestProducts"},"minimum_performance_criteria":{"$ref":"#/components/schemas/SampleRequestPerformanceCriteria"},"maximum_performance_criteria":{"$ref":"#/components/schemas/SampleRequestPerformanceCriteria"},"auto_approval_settings":{"$ref":"#/components/schemas/SampleRequestAutoApprovalSettings"},"target_list":{"items":{"type":"integer"},"type":"array","maxItems":20,"minItems":1,"title":"Target List","description":"List IDs (vault_files.resource_id, integer form). Required (≥1) — sample_request type uses target_list for creator selection, NOT creators_to_include."}},"additionalProperties":false,"type":"object","required":["action","products","target_list"],"title":"SampleRequestPayload","description":"Inner sample_request config — mirrors the legacy\n`sample_request_config` shape exactly (the worker reads from it)."},"SampleRequestPerformanceCriteria":{"properties":{"followers":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Followers"},"avg_views":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Avg Views"},"engagement_rate":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Engagement Rate","description":"Decimal 0-1 (e.g. 0.05 = 5%). Translator scales to 0-100 percent at persistence."},"post_rate":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Post Rate","description":"Decimal 0-1 (e.g. 0.5 = 50%). Translator scales to 0-100 percent at persistence."},"creator_gmv_30_days":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Creator Gmv 30 Days","description":"Creator GMV over the trailing 30 days."},"gmv_per_sample":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Gmv Per Sample","description":"GMV per sample sent — gates whether the creator's prior samples drove enough revenue."}},"additionalProperties":false,"type":"object","title":"SampleRequestPerformanceCriteria","description":"Min/max performance gate for sample-request approval. All fields are\noptional — omit a field to skip that side of the bound.\n\nAPI contract uses unprefixed field names + decimal 0-1 scale for rates\n(`engagement_rate=0.05` for 5%). The translator emits the V2 backend's\nasymmetric prefix names + 0-100 percent scale at persistence. See\nmodules/automations/SampleRequestConfigModels.py for the V2 shape:\n  - min side  → `min_followers`, `min_avg_views`, `min_engagement_rate`,\n                `min_post_rate`, `min_creator_gmv_30_days`, `min_gmv_per_sample`\n  - max side  → `maximum_followers`, `maximum_avg_views`,\n                `maximum_engagement_rate`, `maximum_post_rate`,\n                `maximum_creator_gmv`, `maximum_gmv_per_sample`"},"SampleRequestProductCap":{"properties":{"product_id":{"type":"string","title":"Product Id","description":"TikTok Shop product ID (numeric string)."},"max_approvals_per_week":{"type":"integer","maximum":10000.0,"minimum":0.0,"title":"Max Approvals Per Week","description":"Per-product weekly auto-approval cap. Matches the V2 backend field name (`SampleRequestConfigModels.SelectedProduct.max_approvals_per_week`); 0 = no auto-approvals for this product."}},"additionalProperties":false,"type":"object","required":["product_id","max_approvals_per_week"],"title":"SampleRequestProductCap"},"SampleRequestProducts":{"properties":{"include_all_products":{"type":"boolean","title":"Include All Products","default":false},"selected_products":{"items":{"$ref":"#/components/schemas/SampleRequestProductCap"},"type":"array","maxItems":100,"title":"Selected Products"},"weekly_auto_approval_per_product":{"anyOf":[{"type":"integer","maximum":10000.0,"minimum":1.0},{"type":"null"}],"title":"Weekly Auto Approval Per Product","description":"Per-product weekly cap that applies to ALL shop products (only meaningful when `include_all_products=True`)."}},"additionalProperties":false,"type":"object","title":"SampleRequestProducts","description":"Sample-request product targeting. Two modes:\n  * `include_all_products=True` → all shop products eligible; supply\n    `weekly_auto_approval_per_product` for the global per-product cap.\n  * `include_all_products=False` → enumerate `selected_products` with\n    per-product `max_approvals_per_week` caps.\n\nMirrors the legacy `sample_request_config.products` shape exactly so\nFE-built and API-built automations are interchangeable to the worker."},"SamplesByProductPaginatedResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/SampleByProductItem"},"type":"array","title":"Data"},"currency":{"type":"string","title":"Currency"},"pagination":{"title":"Pagination"},"date_range":{"anyOf":[{"$ref":"#/components/schemas/DateRange"},{"type":"null"}]},"shops_queried":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Shops Queried","description":"Shop names included when querying multiple shops (x-shop-id: all or comma-separated)."}},"type":"object","required":["data","currency","pagination"],"title":"SamplesByProductPaginatedResponse"},"SamplesByProductRequest":{"properties":{"page":{"type":"integer","minimum":1.0,"title":"Page","default":1},"page_size":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Page Size","default":50},"sort_by":{"anyOf":[{"type":"string","enum":["total_requests","approved","sample_gmv"]},{"type":"null"}],"title":"Sort By"},"sort_dir":{"type":"string","enum":["asc","desc"],"title":"Sort Dir","default":"desc"},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"}},"additionalProperties":false,"type":"object","title":"SamplesByProductRequest","description":"POST /samples/by-product request body."},"SamplesListPaginatedResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/SampleListItem"},"type":"array","title":"Data"},"currency":{"type":"string","title":"Currency"},"pagination":{"title":"Pagination"},"date_range":{"anyOf":[{"$ref":"#/components/schemas/DateRange"},{"type":"null"}]},"shops_queried":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Shops Queried","description":"Shop names included when querying multiple shops (x-shop-id: all or comma-separated)."}},"type":"object","required":["data","currency","pagination"],"title":"SamplesListPaginatedResponse"},"SamplesListRequest":{"properties":{"page":{"type":"integer","minimum":1.0,"title":"Page","default":1},"page_size":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Page Size","default":50},"sort_by":{"anyOf":[{"type":"string","enum":["updated_at","created_at","gmv"]},{"type":"null"}],"title":"Sort By"},"sort_dir":{"type":"string","enum":["asc","desc"],"title":"Sort Dir","default":"desc"},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"creator_handle":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Creator Handle"},"product_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Id"}},"additionalProperties":false,"type":"object","title":"SamplesListRequest","description":"POST /samples/list request body."},"SentDmGroup":{"properties":{"creator_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Creator Name"},"creator_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Creator Id"},"last_sent_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Sent At"},"messages":{"items":{"$ref":"#/components/schemas/SentDmMessage"},"type":"array","title":"Messages"}},"type":"object","title":"SentDmGroup"},"SentDmMessage":{"properties":{"content":{"type":"string","title":"Content","description":"Exact rendered text as sent (placeholders substituted)"},"sent_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sent At"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Send-log role, e.g. BOT_INITIAL_MESSAGE / follow-up statuses"}},"type":"object","required":["content"],"title":"SentDmMessage"},"SentDmSection":{"properties":{"exact_copy":{"type":"boolean","const":true,"title":"Exact Copy","description":"True — DM copy is the rendered per-creator sent text","default":true},"groups":{"items":{"$ref":"#/components/schemas/SentDmGroup"},"type":"array","title":"Groups"},"creators_returned":{"type":"integer","title":"Creators Returned"},"total_creators":{"type":"integer","title":"Total Creators"},"total_messages":{"type":"integer","title":"Total Messages"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message","description":"Explicit empty state when nothing was sent"}},"type":"object","required":["creators_returned","total_creators","total_messages"],"title":"SentDmSection"},"SentTcSection":{"properties":{"exact_copy":{"type":"boolean","const":false,"title":"Exact Copy","description":"False — TC per-creator rendered copy is not stored; template is the sent copy","default":false},"coverage_note":{"type":"string","title":"Coverage Note"},"template_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template Text"},"placeholders_present":{"type":"boolean","title":"Placeholders Present","default":false},"invitations":{"items":{"$ref":"#/components/schemas/TcInvitationBatch"},"type":"array","title":"Invitations"},"invitations_returned":{"type":"integer","title":"Invitations Returned"},"total_invitations":{"type":"integer","title":"Total Invitations","description":"Batches with a sent status (NEW_INVITATION / EDITED_INVITATION); excludes skipped/failed."},"total_creators_invited":{"type":"integer","title":"Total Creators Invited","description":"Creators across sent-status batches only. See status_breakdown for skipped/failed."},"status_breakdown":{"additionalProperties":{"type":"integer"},"type":"object","title":"Status Breakdown","description":"Batch count per target_collabs status across ALL rows (sent, skipped=PREVIOUS_INVITATION, INVALID_CREATORS, etc.)."},"campaign":{"anyOf":[{"$ref":"#/components/schemas/TcCampaignLink"},{"type":"null"}],"description":"Campaign linked via campaigns.tc_automation_id, when one exists"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message","description":"Explicit empty state when nothing was sent"}},"type":"object","required":["coverage_note","invitations_returned","total_invitations","total_creators_invited"],"title":"SentTcSection"},"SettlePaymentRequest":{"properties":{"creator_id":{"type":"integer","exclusiveMinimum":0.0,"title":"Creator Id"},"campaign_id":{"type":"integer","exclusiveMinimum":0.0,"title":"Campaign Id"},"amount":{"type":"number","maximum":100000.0,"exclusiveMinimum":0.0,"title":"Amount","description":"Settlement amount in the campaign's currency. Capped at $100,000 per call as a sanity guard."},"currency":{"type":"string","maxLength":3,"minLength":3,"title":"Currency","description":"ISO 4217 currency code (e.g. 'USD'). **Informational** — the shop's persisted wallet currency takes precedence at settlement time. If the wallet currency differs from this value, the transfer executes in the wallet currency without rejection. The response's ``currency`` field confirms the currency actually used for the transfer."},"reward_type":{"type":"string","pattern":"^(cash|custom)$","title":"Reward Type","description":"'cash' triggers a Stripe transfer + wallet debit; 'custom' records the settlement only (no money moves)."},"campaign_creator_mapping_id":{"type":"integer","exclusiveMinimum":0.0,"title":"Campaign Creator Mapping Id","description":"REQUIRED. The campaign-creator mapping id that the settlement is being recorded against. The portal surfaces this id on every creator row in ``GET /campaigns/{id}/detail`` and in the response to the accept endpoint, so callers always have it. Required because the underlying ``PaymentService.settle`` only runs its double-payment guard (``claim_for_settlement_sync``) when a mapping id is present — omitting it would leave the money-moving path with idempotency-only protection (a different ``Idempotency-Key`` per call could debit the wallet repeatedly with no DB-level guard)."},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Free-form metadata stored alongside the settlement (e.g. agent annotation, internal reference)."}},"additionalProperties":false,"type":"object","required":["creator_id","campaign_id","amount","currency","reward_type","campaign_creator_mapping_id"],"title":"SettlePaymentRequest","description":"``POST /public/v1/campaigns/payments/settle`` — pay a creator out.\n\nTriggers a **real Stripe transfer + wallet debit** when\n``reward_type='cash'``; ``reward_type='custom'`` records the\nsettlement only (no money moves). Idempotency-Key is REQUIRED and\nthe inner ``PaymentService.settle`` runs claim/release semantics\nso a retry with the same key can never double-pay."},"SettlementResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"transfer_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transfer Id","description":"Stripe transfer id for cash settlements. Null for custom-reward settlements (no Stripe call)."},"amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Amount"},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"},"paid_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Paid At"},"wallet_balance":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Wallet Balance","description":"Remaining wallet balance after the settlement."}},"additionalProperties":true,"type":"object","required":["success"],"title":"SettlementResponse","description":"201 response for ``POST /campaigns/payments/settle``."},"ShopGmvChannels":{"properties":{"video":{"$ref":"#/components/schemas/ShopGmvVideoChannel"},"live":{"$ref":"#/components/schemas/ShopGmvLiveChannel"},"product_card":{"$ref":"#/components/schemas/ShopGmvProductCardChannel"}},"type":"object","required":["video","live","product_card"],"title":"ShopGmvChannels"},"ShopGmvLiveChannel":{"properties":{"gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gmv","description":"Total live-attributed GMV for the day."},"affiliate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Affiliate","description":"Live GMV from creator/affiliate lives."},"seller":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Seller","description":"Live GMV from the seller's own lives."}},"type":"object","title":"ShopGmvLiveChannel","description":"Live-attributed GMV breakdown for a single time point."},"ShopGmvProductCardChannel":{"properties":{"gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gmv","description":"Total product-card GMV (organic surfaces)."},"shop_tab":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Shop Tab","description":"GMV from customers browsing the shop tab/storefront."},"search":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Search","description":"GMV from TikTok search results."}},"type":"object","title":"ShopGmvProductCardChannel","description":"Product-card (organic) GMV breakdown for a single time point."},"ShopGmvSeriesPoint":{"properties":{"date":{"type":"string","title":"Date","description":"Day (YYYY-MM-DD)."},"gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gmv","description":"Total GMV for the day (before tax, excl. platform co-funding). Sum of channels ≈ gmv (small residuals possible)."},"orders":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Orders","description":"Paid orders that day."},"items_sold":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Items Sold","description":"Units sold that day."},"customers":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Customers","description":"Unique paying customers that day."},"aov":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Aov","description":"Average order value for the day."},"channels":{"$ref":"#/components/schemas/ShopGmvChannels","description":"GMV breakdown by attribution channel."},"traffic":{"$ref":"#/components/schemas/ShopGmvTraffic"}},"type":"object","required":["date","channels","traffic"],"title":"ShopGmvSeriesPoint","description":"One day in the shop-GMV time series."},"ShopGmvSummaryRequest":{"properties":{"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date","description":"Inclusive window start (YYYY-MM-DD). Defaults to 29 days before ``end_date``."},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date","description":"Inclusive window end (YYYY-MM-DD). Defaults to yesterday."}},"additionalProperties":false,"type":"object","title":"ShopGmvSummaryRequest","description":"POST /shop-gmv/summary request body. Window totals across the\nSeller Center daily rollup for one shop."},"ShopGmvSummaryResponse":{"properties":{"shop_id":{"type":"integer","title":"Shop Id"},"currency_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency Code","description":"ISO currency code; ``null`` when window has no data."},"start_date":{"type":"string","title":"Start Date"},"end_date":{"type":"string","title":"End Date"},"day_count":{"type":"integer","title":"Day Count","description":"Number of daily rows that contributed to these totals."},"gmv":{"type":"number","title":"Gmv","description":"SUM of daily GMV across the window."},"orders":{"type":"integer","title":"Orders","description":"SUM of daily orders."},"items_sold":{"type":"integer","title":"Items Sold","description":"SUM of daily units sold."},"customers":{"type":"integer","title":"Customers","description":"SUM of daily unique-customer counts. NOTE: this double-counts buyers who purchased on multiple days; the source rollup is unique-per-day. A true window-distinct count is not derivable from this table."},"aov":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Aov","description":"Window-level AOV computed as total GMV / total orders over the window. ``null`` when there are no orders. Note: this is NOT the average of daily AOV values (which would be meaningless since daily AOVs have different denominators)."},"channels":{"$ref":"#/components/schemas/ShopGmvChannels","description":"SUMmed channel breakdown across the window."},"traffic":{"$ref":"#/components/schemas/ShopGmvTraffic"}},"type":"object","required":["shop_id","start_date","end_date","day_count","gmv","orders","items_sold","customers","channels","traffic"],"title":"ShopGmvSummaryResponse","description":"Window totals across the Seller Center daily rollup for one shop."},"ShopGmvTimeseriesRequest":{"properties":{"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date","description":"Inclusive window start (YYYY-MM-DD). Defaults to 29 days before ``end_date`` (30-day window). The Seller Center data is back-filled ~90 days per shop, so older ranges will return an empty series."},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date","description":"Inclusive window end (YYYY-MM-DD). Defaults to yesterday (today is excluded because the daily row is still incomplete)."}},"additionalProperties":false,"type":"object","title":"ShopGmvTimeseriesRequest","description":"POST /shop-gmv/timeseries request body. Full Seller Center shop GMV\n(ads + organic + affiliate) — single shop, daily grain."},"ShopGmvTimeseriesResponse":{"properties":{"shop_id":{"type":"integer","title":"Shop Id"},"currency_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency Code","description":"ISO currency code applying to every point in the series (e.g. 'GBP', 'USD'). ``null`` when the series is empty or (defensively) when the source rows somehow mix currencies for the same shop — in that case GMV values should not be summed in the client."},"granularity":{"type":"string","title":"Granularity","description":"Always 'daily' in v1.","default":"daily"},"start_date":{"type":"string","title":"Start Date"},"end_date":{"type":"string","title":"End Date"},"series":{"items":{"$ref":"#/components/schemas/ShopGmvSeriesPoint"},"type":"array","title":"Series","description":"One entry per day with data in the window. Empty when the shop has no Seller Center data for this range (collection is rolling out — empty is a normal, non-error state)."}},"type":"object","required":["shop_id","start_date","end_date","series"],"title":"ShopGmvTimeseriesResponse","description":"Daily shop-GMV time series from the Seller Center scrape. Single\nshop only; ``currency_code`` is per-shop and applies to every point."},"ShopGmvTraffic":{"properties":{"product_impressions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Product Impressions","description":"Product impressions across surfaces."},"product_clicks":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Product Clicks","description":"Product clicks across surfaces."}},"type":"object","title":"ShopGmvTraffic"},"ShopGmvVideoChannel":{"properties":{"gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gmv","description":"Total video-attributed GMV for the day."},"affiliate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Affiliate","description":"Video GMV from creator/affiliate videos."},"seller":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Seller","description":"Video GMV from the seller's own videos."}},"type":"object","title":"ShopGmvVideoChannel","description":"Video-attributed GMV breakdown for a single time point."},"ShopResponse":{"properties":{"shop_id":{"type":"integer","title":"Shop Id"},"shop_name":{"type":"string","title":"Shop Name"},"region":{"type":"string","title":"Region"},"currency":{"type":"string","title":"Currency"},"status":{"type":"string","title":"Status"},"primary_category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Category","description":"Shop's primary L1 product category from TikTok Shop. Matches one of the canonical L1 names returned by GET /api/social-intelligence-v2/sellers/categories. Null when the shop has no Social Intelligence data yet."}},"type":"object","required":["shop_id","shop_name","region","currency","status"],"title":"ShopResponse"},"ShopSupportContactDefaultRequest":{"properties":{"email":{"type":"string","maxLength":255,"minLength":3,"title":"Email","description":"Default contact email shown to the creator on the TC card."},"phone":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Phone","description":"Optional default contact phone. E.164 format recommended."}},"additionalProperties":false,"type":"object","required":["email"],"title":"ShopSupportContactDefaultRequest","description":"Body for PUT /public/v1/target-collabs/support-contact-default (CORE-5428).\n\nSets the shop-level default TC support contact. `email` is required;\n`phone` is optional (TikTok's TC card accepts an email-only contact).\nValidation mirrors `TargetCollabContact` so a stored default always\nsatisfies the TC create surfaces it falls back into."},"ShopSupportContactDefaultResponse":{"properties":{"shop_id":{"type":"integer","title":"Shop Id","description":"Shop the default applies to."},"configured":{"type":"boolean","title":"Configured","description":"True when a default is stored for this shop. When False, TC create requests must supply `support_contact` per request."},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Default contact email, or null when not configured."},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Default contact phone, or null when unset/not configured."},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At","description":"ISO-8601 timestamp of the last update, or null when not configured."}},"type":"object","required":["shop_id","configured"],"title":"ShopSupportContactDefaultResponse","description":"Shop-level default TC support contact (CORE-5428).\n\nReturned by GET and PUT /public/v1/target-collabs/support-contact-default.\n`configured` is False (and email/phone/updated_at are null) when the shop\nhas no default set — in that state the TC create surfaces still require a\nper-request `support_contact`."},"ShopsListResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ShopResponse"},"type":"array","title":"Data"}},"type":"object","required":["data"],"title":"ShopsListResponse"},"SparkCodeSyncAllRequestPublic":{"properties":{},"additionalProperties":false,"type":"object","title":"SparkCodeSyncAllRequestPublic","description":"``POST /public/v1/gmv-max/spark-codes/sync-all`` — queue every\nunsynced code for the shop. Returns immediately with a queue count;\nthe offline CRJ actually fires the TikTok calls.\n\nHas no body fields (shop_id comes from `x-shop-id`); kept as a model\nso we can still ``extra=\"forbid\"`` against accidental payloads."},"SparkCodeSyncRequestPublic":{"properties":{"spark_codes":{"items":{"type":"string"},"type":"array","maxItems":100,"minItems":1,"title":"Spark Codes","description":"Up to 100 spark codes to sync in one request. Each code fires one TikTok `/tt_video/authorize/` call; rate-limited codes are left as `pending` so the offline CRJ picks them up. Whitespace is stripped; blank or duplicate entries are rejected with 422."}},"additionalProperties":false,"type":"object","required":["spark_codes"],"title":"SparkCodeSyncRequestPublic","description":"``POST /public/v1/gmv-max/spark-codes/sync`` — sync specific codes\nimmediately. Calls TikTok synchronously and returns per-code results."},"TargetCollabConfig":{"properties":{"invitation_name":{"type":"string","maxLength":30,"minLength":1,"title":"Invitation Name","description":"Name shown on the TC invitation card (max 30 chars). Required by the portal — without it, the card displays empty. Persists as `config.BASE_INVITATION_NAME`."},"valid_until":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Valid Until","description":"Date the TC card stops being valid. After this, the dispatcher stops the automation (see `internal/queries/automations_to_run.py`). Null = no expiry. Distinct from `end_date` (top-level automation stop date) — they can differ. Persists as `config.VALID_UNTIL` in MM/DD/YYYY format (legacy)."},"message":{"type":"string","maxLength":500,"minLength":1,"title":"Message","description":"TC card message text shown to the creator (max 500 chars). Different from `messages[]` (initial outreach DM addons) — this is the body on the invitation card itself. The portal rejects bodies containing the word `amazon`. Persists as `config.TARGET_COLLAB_MESSAGE`."},"products":{"items":{"$ref":"#/components/schemas/TargetCollabProduct"},"type":"array","maxItems":50,"minItems":1,"title":"Products"},"content_type":{"type":"string","enum":["no_preference","shoppable_video","shoppable_live"],"title":"Content Type","description":"Content type preference for this TC. Translates to the legacy DB enum (`No preference` / `Shoppable video` / `Shoppable LIVE`) at the persistence boundary.","default":"no_preference"},"sample_policy":{"$ref":"#/components/schemas/TargetCollabSamplePolicy"},"support_contact":{"anyOf":[{"$ref":"#/components/schemas/TargetCollabContact"},{"type":"null"}],"description":"Brand-side support contact shown on the TC card. Optional ONLY when this shop has a default configured via `PUT /public/v1/target-collabs/support-contact-default` (CORE-5428) — in that case it's used as the fallback. When provided here it overrides the shop default for this request. If neither is present the request is rejected with 422 `SUPPORT_CONTACT_REQUIRED` (the portal also rejects TC creates without a contact email)."}},"additionalProperties":false,"type":"object","required":["invitation_name","message","products"],"title":"TargetCollabConfig","description":"TC-specific config — invitation metadata, commission rates, sample\npolicy, content type, support contact. Maps to the legacy TC config\nblob shape (UPPERCASE root keys: BASE_INVITATION_NAME, VALID_UNTIL,\nTARGET_COLLAB_MESSAGE, EMAIL, PHONE_NUMBER, OFFER_FREE_SAMPLES,\nAUTO_APPROVE, PRODUCTS) — the worker reads from those, so the\ntranslator flattens this nested model accordingly."},"TargetCollabContact":{"properties":{"email":{"type":"string","maxLength":255,"minLength":3,"title":"Email","description":"Contact email shown to the creator on the TC card."},"phone":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Phone","description":"E.164 format recommended."}},"additionalProperties":false,"type":"object","required":["email"],"title":"TargetCollabContact","description":"Brand-side contact info displayed on the TC card so the creator\ncan reach the merchant with questions. `email` is required by the\nportal at TC create time; `phone` is optional. Persists at the legacy\nconfig root as `EMAIL` / `PHONE_NUMBER`."},"TargetCollabContactPatch":{"properties":{"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"}},"additionalProperties":false,"type":"object","title":"TargetCollabContactPatch"},"TargetCollabDirectRequest":{"properties":{"creator_handle":{"type":"string","maxLength":64,"minLength":1,"title":"Creator Handle","description":"TikTok @-handle (without the `@`). Resolved server-side against this shop's CRM / affiliate list (a row in ``crm_creators`` for ``reacher_shop_id=<shop_id>`` whose ``creator_name`` matches the handle, ordered by GMV via a LEFT JOIN against the region's creator table). The creator must already have interacted with your shop — handles for creators who haven't applied / sampled / transacted yet will not resolve, even if the handle exists globally on TikTok. Unknown handle → 404 ``CREATOR_NOT_FOUND``."},"product_id":{"type":"string","maxLength":64,"minLength":1,"title":"Product Id","description":"TikTok Shop product ID to attach to the invitation."},"commission_rate":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Commission Rate","description":"Affiliate commission rate (0.0-1.0, e.g. 0.20 for 20%)."},"shop_ads_commission_rate":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Shop Ads Commission Rate","description":"Optional Shop Ads (co-funded) commission rate (0.0-1.0). When set, persists into the legacy PRODUCTS blob at the per-product level; the TC runtime fills the matching field on the TC form. Omit to leave unset (worker treats as not configured)."},"message":{"type":"string","maxLength":500,"minLength":1,"title":"Message","description":"TC card message body shown to the creator (max 500 chars). Mirrors `target_collab.message` on the full TC create surface — the portal rejects bodies containing the word `amazon`."},"invitation_name":{"type":"string","maxLength":30,"minLength":1,"title":"Invitation Name","description":"Name shown on the TC invitation card (max 30 chars). Same field as `target_collab.invitation_name` on the full TC create."},"valid_until":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Valid Until","description":"Date the TC card stops being valid. Defaults to today + 30 days when omitted. After this date the dispatcher stops the underlying automation (see `internal/queries/automations_to_run.py`)."},"support_contact":{"anyOf":[{"$ref":"#/components/schemas/TargetCollabDirectSupportContact"},{"type":"null"}],"description":"Brand-side support contact shown on the TC card. Optional ONLY when this shop has a default configured via `PUT /public/v1/target-collabs/support-contact-default` (CORE-5428) — in that case it's used as the fallback. When provided here it overrides the shop default for this request. If neither is present the request is rejected with 422 `SUPPORT_CONTACT_REQUIRED`."}},"additionalProperties":false,"type":"object","required":["creator_handle","product_id","commission_rate","message","invitation_name"],"title":"TargetCollabDirectRequest","description":"Body for POST /target-collabs — direct one-shot TC invite.\n\nTranslates internally to:\n  1. AutomationTargetCollabRequest with a single-creator `list_upload`\n     audience and a single-product PRODUCTS array, then\n  2. create_target_collab(... created_via=\"api_direct\"), then\n  3. _transition_status(\"running\") with audience materialization.\n\nReturns 201 with `status: \"queued\"` — delivery is asynchronous via\nReacher's TC delivery worker (browser automation, same path the portal\nuses). Poll GET /automations/{automation_id} for delivery status."},"TargetCollabDirectResponse":{"properties":{"automation_id":{"type":"integer","title":"Automation Id","description":"Underlying Automations.automation_id, for polling."},"status":{"type":"string","title":"Status","description":"Always `queued` on success. The underlying automation is `running` immediately; the field name reflects delivery semantics (the TC invite itself is queued for the worker)."},"creator_id":{"type":"string","title":"Creator Id","description":"Resolved creator_id for the supplied handle."},"creator_handle":{"type":"string","title":"Creator Handle","description":"The handle echoed back (stripped of any leading `@`)."},"shop_id":{"type":"integer","title":"Shop Id","description":"Shop the invite was created under."},"check_status_at":{"type":"string","title":"Check Status At","description":"Relative URL for polling automation state — `/public/v1/automations/{automation_id}`."},"queued_at":{"type":"string","title":"Queued At","description":"ISO-8601 timestamp at which the start transition fired."},"dry_run":{"type":"boolean","title":"Dry Run","description":"True when X-Dry-Run was set — no automation row was created and no worker job was queued. `automation_id` is `0` and `queued_at` reflects the request time.","default":false}},"type":"object","required":["automation_id","status","creator_id","creator_handle","shop_id","check_status_at","queued_at"],"title":"TargetCollabDirectResponse","description":"201 envelope for POST /target-collabs.\n\nQueued-semantics surface — the POST returns immediately after persisting\nthe underlying automation row and flipping it to `running`. Actual TC\ndelivery happens asynchronously via the tc_v5 worker (browser\nautomation). Poll GET /automations/{automation_id} for delivery status."},"TargetCollabDirectSupportContact":{"properties":{"email":{"type":"string","maxLength":255,"minLength":3,"title":"Email","description":"Contact email shown to the creator on the TC card."},"phone":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Phone","description":"E.164 format recommended. Normalized at persistence."}},"additionalProperties":false,"type":"object","required":["email"],"title":"TargetCollabDirectSupportContact","description":"Brand-side support contact shown on the TC invitation card. Mirrors\n`TargetCollabContact` exactly — kept as a separate class so the direct\nsurface's OpenAPI doesn't pull in the heavier automation-shape model\nname when callers only see this endpoint."},"TargetCollabPatchPayload":{"properties":{"invitation_name":{"anyOf":[{"type":"string","maxLength":30,"minLength":1},{"type":"null"}],"title":"Invitation Name"},"valid_until":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Valid Until"},"message":{"anyOf":[{"type":"string","maxLength":500,"minLength":1},{"type":"null"}],"title":"Message"},"products":{"anyOf":[{"items":{"$ref":"#/components/schemas/TargetCollabProduct"},"type":"array"},{"type":"null"}],"title":"Products"},"content_type":{"anyOf":[{"type":"string","enum":["no_preference","shoppable_video","shoppable_live"]},{"type":"null"}],"title":"Content Type"},"sample_policy":{"anyOf":[{"$ref":"#/components/schemas/TargetCollabSamplePolicyPatch"},{"type":"null"}]},"support_contact":{"anyOf":[{"$ref":"#/components/schemas/TargetCollabContactPatch"},{"type":"null"}]},"lock_receivers_profiles":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Lock Receivers Profiles"}},"additionalProperties":false,"type":"object","title":"TargetCollabPatchPayload","description":"All-Optional companion to `TargetCollabConfig` for PATCH bodies.\n`extra=\"forbid\"` rejects typos so a PATCH like `{invitatoin_name:\n\"...\"}` returns 422 instead of silently no-op'ing."},"TargetCollabProduct":{"properties":{"product_id":{"type":"string","title":"Product Id","description":"TikTok Shop product ID."},"commission_rate":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Commission Rate","description":"Affiliate commission rate (0.0-1.0, e.g. 0.20 for 20%)."},"shop_min_commission":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Shop Min Commission"},"shop_ads_commission_rate":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Shop Ads Commission Rate","description":"Optional Shop Ads (co-funded) commission rate (0.0-1.0). Persisted into the legacy PRODUCTS blob; the TC runtime fills it on the TC form when present."}},"additionalProperties":false,"type":"object","required":["product_id","commission_rate"],"title":"TargetCollabProduct"},"TargetCollabSamplePolicy":{"properties":{"offer_free_samples":{"type":"boolean","title":"Offer Free Samples","default":false},"auto_approve":{"type":"boolean","title":"Auto Approve","description":"When true, accepted invites trigger immediate sample-request auto-approval without manual review. Maps to top-level `config.AUTO_APPROVE`. Per architecture review 2026-05-01.","default":false},"send_to":{"type":"string","enum":["brand_recipient","mall_recipient"],"title":"Send To","default":"brand_recipient"}},"additionalProperties":false,"type":"object","title":"TargetCollabSamplePolicy","description":"TC sample policy. Three modes from the portal:\n\n  1. Manual approval                — `offer_free_samples=False, auto_approve=False`\n  2. Free samples + manual approval — `offer_free_samples=True,  auto_approve=False`\n  3. Free samples + auto approval   — `offer_free_samples=True,  auto_approve=True`\n\n`offer_free_samples=False, auto_approve=True` is also accepted but\nrare in prod (~0% — see audit 2026-05-01)."},"TargetCollabSamplePolicyPatch":{"properties":{"offer_free_samples":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Offer Free Samples"},"auto_approve":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Auto Approve"},"send_to":{"anyOf":[{"type":"string","enum":["brand_recipient","mall_recipient"]},{"type":"null"}],"title":"Send To"}},"additionalProperties":false,"type":"object","title":"TargetCollabSamplePolicyPatch"},"TargetCollaborationCardContent":{"properties":{"invitation_group_id":{"type":"string","title":"Invitation Group Id"}},"type":"object","required":["invitation_group_id"],"title":"TargetCollaborationCardContent","description":"Target collaboration card-specific content fields."},"TcCampaignLink":{"properties":{"campaign_id":{"type":"integer","title":"Campaign Id"},"campaign_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campaign Name"}},"type":"object","required":["campaign_id"],"title":"TcCampaignLink"},"TcInvitationBatch":{"properties":{"sent_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sent At"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"creator_count":{"type":"integer","title":"Creator Count"},"creators":{"items":{"type":"string"},"type":"array","title":"Creators"},"creators_truncated":{"type":"boolean","title":"Creators Truncated","default":false},"expiration_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expiration Date"}},"type":"object","required":["creator_count"],"title":"TcInvitationBatch"},"TimeseriesDataPoint":{"properties":{"date":{"type":"string","title":"Date"},"value":{"type":"number","title":"Value"}},"type":"object","required":["date","value"],"title":"TimeseriesDataPoint"},"TimeseriesRequest":{"properties":{"metrics":{"items":{"type":"string","enum":["gmv","total_gmv","creators","videos_posted","video_views","gmv_per_video","gmv_per_sample","creators_reached","creators_messaged","tc_invites_sent","samples_approved","sample_requests","gmv_driving_videos","new_creators_posting","open_collabs","accepted_tc_count","emails_sent","dm_responses","reply_rate","orders","units_sold","aov","ctr","conversion_rate"]},"type":"array","maxItems":24,"minItems":1,"title":"Metrics"},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"granularity":{"anyOf":[{"type":"string","enum":["day","week","month"]},{"type":"null"}],"title":"Granularity"}},"additionalProperties":false,"type":"object","required":["metrics"],"title":"TimeseriesRequest","description":"POST /metrics/timeseries request body."},"TimeseriesResponse":{"properties":{"data":{"additionalProperties":{"items":{"$ref":"#/components/schemas/TimeseriesDataPoint"},"type":"array"},"type":"object","title":"Data"},"granularity":{"type":"string","title":"Granularity"},"start_date":{"type":"string","title":"Start Date"},"end_date":{"type":"string","title":"End Date"},"currency":{"type":"string","title":"Currency"},"shops_queried":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Shops Queried","description":"Shop names included when querying multiple shops (x-shop-id: all or comma-separated)."}},"type":"object","required":["data","granularity","start_date","end_date","currency"],"title":"TimeseriesResponse"},"TrackerVideosRequest":{"properties":{"campaign_creator_mapping_id":{"type":"integer","exclusiveMinimum":0.0,"title":"Campaign Creator Mapping Id"}},"additionalProperties":false,"type":"object","required":["campaign_creator_mapping_id"],"title":"TrackerVideosRequest","description":"``POST /public/v1/campaigns/content/tracker-vids`` — list a\ncreator-campaign mapping's posted videos (used by the settlement\nscreen on the portal)."},"TrackerVideosResponse":{"properties":{"status":{"type":"string","title":"Status","default":"success"},"data":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Data"},"count":{"type":"integer","title":"Count","default":0}},"type":"object","title":"TrackerVideosResponse","description":"200 response for ``POST /campaigns/content/tracker-vids``."},"TriggerFrequency":{"type":"string","enum":["daily"],"title":"TriggerFrequency","description":"How often the automation evaluates its conditions.\n\nOnly ``daily`` is honoured by the offline execution engine today; the\nportal FE submits this value exclusively."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VideoContentResponse":{"properties":{"status":{"type":"string","title":"Status","default":"success"},"data":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Data"},"count":{"type":"integer","title":"Count","default":0}},"type":"object","title":"VideoContentResponse","description":"200 response for ``POST /campaigns/content/view``."},"VideoCreativeBreakdown":{"properties":{"analyzed":{"type":"boolean","title":"Analyzed"},"hook":{"anyOf":[{"$ref":"#/components/schemas/VideoCreativeHook"},{"type":"null"}]},"sell_points":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Sell Points","description":"Key selling points the video leans on"},"product_niche":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Niche","description":"Product category leaf from the catalog, falling back to the content-angle tag"},"shot_style":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Shot Style","description":"How the video is framed and composed"},"videography":{"anyOf":[{"$ref":"#/components/schemas/VideoCreativeVideography"},{"type":"null"}]},"missing_fields":{"items":{"type":"string"},"type":"array","title":"Missing Fields"}},"type":"object","required":["analyzed"],"title":"VideoCreativeBreakdown","description":"AI creative analysis of the video (customers.video_analysis).\n\nNot every video has been analyzed — when ``analyzed`` is false, or when\nindividual fields are null, ``missing_fields`` names the gaps so clients\ncan render \"not analyzed\" instead of blanks."},"VideoCreativeHook":{"properties":{"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text","description":"The hook line as spoken/shown"},"classification":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Classification","description":"Hook type, e.g. problem-solution, curiosity"},"reasoning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reasoning","description":"Why the analysis classified it this way"}},"type":"object","title":"VideoCreativeHook","description":"Opening-seconds angle of the video."},"VideoCreativeItem":{"properties":{"video_id":{"type":"string","title":"Video Id"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"creator_handle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Creator Handle"},"video_gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Video Gmv","description":"GMV earned in the date range (not lifetime)"},"views":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Views","description":"Views in the date range (not lifetime)"},"like_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Like Count"},"comment_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Comment Count"},"order_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Order Count"},"posted_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Posted Date"},"rank":{"type":"integer","title":"Rank"},"creative":{"$ref":"#/components/schemas/VideoCreativeBreakdown"}},"type":"object","required":["video_id","rank","creative"],"title":"VideoCreativeItem","description":"Video performance row enriched with the creative breakdown."},"VideoCreativeVideography":{"properties":{"locations":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Locations"},"lighting":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Lighting"},"product_showcase":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Product Showcase"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-text videography notes (older analyses store prose instead of structured fields)"}},"type":"object","title":"VideoCreativeVideography","description":"Camera work, setting, and presentation patterns."},"VideoItem":{"properties":{"video_id":{"type":"string","title":"Video Id"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"video_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Url"},"creator_handle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Creator Handle"},"product_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Id"},"product_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Name"},"views":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Views"},"like_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Like Count"},"comment_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Comment Count"},"share_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Share Count"},"units_sold":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Units Sold"},"video_gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Video Gmv"},"posted_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Posted Date"}},"type":"object","required":["video_id"],"title":"VideoItem"},"VideoPerformanceItem":{"properties":{"video_id":{"type":"string","title":"Video Id"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"creator_handle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Creator Handle"},"video_gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Video Gmv","description":"GMV earned in the date range (not lifetime)"},"views":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Views","description":"Views in the date range (not lifetime)"},"like_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Like Count"},"comment_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Comment Count"},"order_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Order Count"},"posted_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Posted Date"}},"type":"object","required":["video_id"],"title":"VideoPerformanceItem","description":"Video with period-specific metrics from video_info (daily table)."},"VideoPerformanceRequest":{"properties":{"page":{"type":"integer","minimum":1.0,"title":"Page","default":1},"page_size":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Page Size","default":50},"sort_by":{"anyOf":[{"type":"string","enum":["video_gmv","views","like_count","order_count","posted_date"]},{"type":"null"}],"title":"Sort By"},"sort_dir":{"type":"string","enum":["asc","desc"],"title":"Sort Dir","default":"desc"},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"creator_handle":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Creator Handle"},"min_gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Gmv"},"min_views":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Views"}},"additionalProperties":false,"type":"object","title":"VideoPerformanceRequest","description":"POST /videos/performance request body."},"VideoPerformanceResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/VideoPerformanceItem"},"type":"array","title":"Data"},"currency":{"type":"string","title":"Currency"},"pagination":{"title":"Pagination"},"date_range":{"anyOf":[{"$ref":"#/components/schemas/DateRange"},{"type":"null"}]},"shops_queried":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Shops Queried","description":"Shop names included when querying multiple shops (x-shop-id: all or comma-separated)."}},"type":"object","required":["data","currency","pagination"],"title":"VideoPerformanceResponse"},"VideosCreativeCoverage":{"properties":{"videos_returned":{"type":"integer","title":"Videos Returned"},"with_creative_analysis":{"type":"integer","title":"With Creative Analysis"}},"type":"object","required":["videos_returned","with_creative_analysis"],"title":"VideosCreativeCoverage"},"VideosCreativeRequest":{"properties":{"limit":{"type":"integer","maximum":50.0,"minimum":1.0,"title":"Limit","default":10},"sort_by":{"anyOf":[{"type":"string","enum":["video_gmv","views","like_count","order_count","posted_date"]},{"type":"null"}],"title":"Sort By"},"sort_dir":{"type":"string","enum":["asc","desc"],"title":"Sort Dir","default":"desc"},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"}},"additionalProperties":false,"type":"object","title":"VideosCreativeRequest","description":"POST /videos/creative request body."},"VideosCreativeResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/VideoCreativeItem"},"type":"array","title":"Data"},"currency":{"type":"string","title":"Currency"},"date_range":{"anyOf":[{"$ref":"#/components/schemas/DateRange"},{"type":"null"}]},"coverage":{"$ref":"#/components/schemas/VideosCreativeCoverage"},"total_count":{"type":"integer","title":"Total Count","description":"Total videos with performance data in the window (not just the top N)"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message","description":"Set when there are no videos in the window (explicit empty state)"},"shops_queried":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Shops Queried","description":"Shop names included when querying multiple shops (x-shop-id: all or comma-separated)."}},"type":"object","required":["data","currency","coverage","total_count"],"title":"VideosCreativeResponse"},"VideosListPaginatedResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/VideoItem"},"type":"array","title":"Data"},"currency":{"type":"string","title":"Currency"},"pagination":{"title":"Pagination"},"date_range":{"anyOf":[{"$ref":"#/components/schemas/DateRange"},{"type":"null"}]},"shops_queried":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Shops Queried","description":"Shop names included when querying multiple shops (x-shop-id: all or comma-separated)."}},"type":"object","required":["data","currency","pagination"],"title":"VideosListPaginatedResponse"},"VideosListRequest":{"properties":{"page":{"type":"integer","minimum":1.0,"title":"Page","default":1},"page_size":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Page Size","default":50},"sort_by":{"anyOf":[{"type":"string","enum":["video_gmv","views","units_sold","posted_date","like_count"]},{"type":"null"}],"title":"Sort By"},"sort_dir":{"type":"string","enum":["asc","desc"],"title":"Sort Dir","default":"desc"},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"creator_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Creator Id"},"creator_handle":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Creator Handle"},"product_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product Id"},"min_gmv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Gmv"},"min_views":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Views"}},"additionalProperties":false,"type":"object","title":"VideosListRequest","description":"POST /videos/list request body."},"ViewVideoContentRequest":{"properties":{"video_id":{"type":"string","maxLength":64,"minLength":1,"title":"Video Id"}},"additionalProperties":false,"type":"object","required":["video_id"],"title":"ViewVideoContentRequest","description":"``POST /public/v1/campaigns/content/view`` — fetch a single video's\ndetail by id."},"WeightableMetric":{"type":"string","enum":["gmv","avg_views_per_post","views","videos_posted","units_sold"],"title":"WeightableMetric","description":"Video metrics that can carry a weight in a weighted leaderboard."},"WeightedMetricItem":{"properties":{"metric_type":{"$ref":"#/components/schemas/WeightableMetric"},"weight":{"type":"integer","maximum":100.0,"minimum":0.0,"title":"Weight"}},"additionalProperties":false,"type":"object","required":["metric_type","weight"],"title":"WeightedMetricItem","description":"One metric + weight entry in a weighted leaderboard.\n\n``extra=\"forbid\"``: template pricing JSONB round-trips through the\nmicro-challenge materializer — unknown keys must not smuggle through."},"WriteErrorResponse":{"properties":{"error":{"additionalProperties":true,"type":"object","title":"Error"}},"type":"object","required":["error"],"title":"WriteErrorResponse","description":"Documented error envelope for write-endpoint failures.\n\n`error.code` values customers should expect:\n  - IDEMPOTENCY_KEY_REQUIRED   400 — POST/DELETE without the header\n  - IDEMPOTENCY_KEY_TOO_LONG   400 — header > 255 chars\n  - IDEMPOTENCY_KEY_REUSED     409 — same key, different body\n  - IDEMPOTENCY_KEY_IN_FLIGHT  409 — same key, prior call still running\n  - INVALID_REQUEST            400 — Pydantic validation or forbidden fields\n  - WRITE_NOT_PERMITTED        403 — api_key lacks 'read_write' scope\n  - WRITE_RATE_LIMIT_EXCEEDED  429 — write tier exceeded\n  - AUTOMATION_ARCHIVED        409 — operation blocked, row is archived\n  - NOT_FOUND                  404 — automation_id not in this shop"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"x-api-key","description":"Your Reacher API key (starts with rk_live_)"},"ShopId":{"type":"apiKey","in":"header","name":"x-shop-id","description":"The shop ID to query. Accepts a single integer, a comma-separated list of integers (e.g. '101,202,303'), or 'all' to query every shop your API key has access to. Multi-shop responses include a 'shops_queried' field and use USD as the default currency. Detail endpoints (e.g. GET /creators/{handle}, GET /automations/{id}) require a single shop ID."}}},"tags":[{"name":"Shops","description":"Discover your shops and their regions."},{"name":"Creators","description":"Affiliates who have worked with your shop."},{"name":"Products","description":"Your products and their affiliate performance."},{"name":"Videos","description":"Affiliate videos and performance metrics."},{"name":"Samples","description":"Sample request tracking and per-product breakdowns."},{"name":"Automations","description":"Automation performance and outreach metrics."},{"name":"Personalization","description":"AI-personalized outreach (CORE-4857): preview the message a creator would receive, resolve product blocks, retone copy, and read fallback stats + per-creator sent history. Configure it on a DM via the `personalization` block of POST /automations/dm. Single-shop only."},{"name":"Target Collabs","description":"Send a single TikTok Shop Target Collab invitation to one creator by handle — convenience surface over the two-step automation create + start. For >10 invites at a time, use POST /automations/target-collab with a multi-creator audience instead. Also exposes GET/PUT /target-collabs/support-contact-default to configure a shop-level default `support_contact`, which the TC create surfaces fall back to when a request omits it."},{"name":"Metrics","description":"Time-series metrics for dashboards and charts."},{"name":"Shop GMV","description":"Full TikTok Seller Center shop GMV (ads + organic + affiliate) — daily series and window totals with channel breakdown. Distinct from POST /metrics/timeseries metric=gmv, which is affiliate-attributed only."},{"name":"Analytics","description":"Affiliate funnel and creator level distributions."},{"name":"GMV Max","description":"TikTok Smart+ (GMV Max) campaigns and daily performance metrics for your shop."},{"name":"AI Search","description":"Natural-language creator search backed by the same retrieval pipeline as the portal. Search and CSV export are both available, with a 50,000-row hard cap per export."},{"name":"Creator Messages","description":"Read the shop's creator DM inbox and reply to creators programmatically (e.g. from an AI agent). Replies require the read_write scope and only work in conversations a creator has already opened."},{"name":"Campaigns","description":"Create and manage Creator Community Campaigns — Retainer, Challenge, Leaderboard, and Live GMV Challenge types. Full surface: CRUD, creator accept/reject/reactivate/remove, cross-shop add-to-campaign (move) with region + same-customer guards, content tracking, request-more-content, deposit-intent + settlement with full idempotency + dry-run + audit logging on the money-moving path."}],"security":[{"ApiKeyAuth":[],"ShopId":[]}]}