Download OpenAPI specification:
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.
Returns all active shops accessible to this API key. Use the returned shop_id values in the x-shop-id header for other endpoints.
{- "data": [
- {
- "shop_id": 0,
- "shop_name": "string",
- "region": "string",
- "affiliate_market_region": "string",
- "business_region": "string",
- "currency": "string",
- "status": "string",
- "customer_id": 0,
- "primary_category": "string"
}
]
}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.
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 50 |
Sort By (string) or Sort By (null) (Sort By) | |
| sort_dir | string (Sort Dir) Default: "desc" Enum: "asc" "desc" |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) | |
Status (string) or Status (null) (Status) | |
Creator Handle (string) or Creator Handle (null) (Creator Handle) | |
Min Shop Gmv (number) or Min Shop Gmv (null) (Min Shop Gmv) | |
Min Overall Gmv (number) or Min Overall Gmv (null) (Min Overall Gmv) | |
Min Videos (integer) or Min Videos (null) (Min Videos) | |
Min Followers (integer) or Min Followers (null) (Min Followers) | |
Group Id (string) or Group Id (null) (Group Id) | |
Array of Tags (strings) or Tags (null) (Tags) | |
Product Id (string) or Product Id (null) (Product Id) |
{- "page": 1,
- "page_size": 50,
- "sort_by": "shop_gmv",
- "sort_dir": "asc",
- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "status": "string",
- "creator_handle": "string",
- "min_shop_gmv": 0,
- "min_overall_gmv": 0,
- "min_videos": 0,
- "min_followers": 0,
- "group_id": "string",
- "tags": [
- "string"
], - "product_id": "string"
}{- "data": [
- {
- "creator_handle": "string",
- "follower_count": 0,
- "status": "string",
- "shop_gmv": 0,
- "overall_gmv": 0,
- "creator_level": "string",
- "overall_fulfillment_rate": 0,
- "shop_units_sold": 0,
- "shop_video_count": 0,
- "video_views": 0,
- "sample_received": 0,
- "commission_rate": 0,
- "est_commission": 0,
- "fulfillment_rate": 0,
- "tags": [
- "string"
], - "product_id": "string",
- "product_title": "string",
- "updated_at": "string",
- "created_at": "string"
}
], - "currency": "string",
- "pagination": null,
- "date_range": {
- "start_date": "string",
- "end_date": "string"
}, - "shops_queried": [
- "string"
]
}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.
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 50 |
Sort By (string) or Sort By (null) (Sort By) | |
| sort_dir | string (Sort Dir) Default: "desc" Enum: "asc" "desc" |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) | |
Creator Handle (string) or Creator Handle (null) (Creator Handle) | |
Min Gmv (number) or Min Gmv (null) (Min Gmv) |
{- "page": 1,
- "page_size": 50,
- "sort_by": "gmv",
- "sort_dir": "asc",
- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "creator_handle": "string",
- "min_gmv": 0
}{- "data": [
- {
- "creator_id": "string",
- "creator_handle": "string",
- "gmv": 0,
- "units_sold": 0,
- "order_count": 0,
- "est_commission": 0,
- "follower_count": 0
}
], - "currency": "string",
- "pagination": null,
- "date_range": {
- "start_date": "string",
- "end_date": "string"
}, - "shops_queried": [
- "string"
]
}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).
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.
Aggregates cover the whole shop — there is no filter parameter in v1. Defaults to the trailing 28 days. Requires a single shop.
No 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.
Start Date (string) or Start Date (null) (Start Date) Window start (inclusive). Omit for a trailing 28-day window ending at end_date. | |
End Date (string) or End Date (null) (End Date) Window end (inclusive). Defaults to today. |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24"
}{- "data": {
- "new_creators": 0,
- "gmv_generated": 0,
- "video_posts": 0,
- "orders": 0
}, - "currency": "string",
- "date_range": {
- "start_date": "string",
- "end_date": "string"
}
}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.
Every 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.
Defaults to the trailing 28 days. Requires a single shop.
| creator_handles required | Array of strings (Creator Handles) [ 1 .. 100 ] items 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 |
Start Date (string) or Start Date (null) (Start Date) Window start (inclusive). Omit for a trailing 28-day window ending at end_date. | |
End Date (string) or End Date (null) (End Date) Window end (inclusive). Defaults to today. |
{- "creator_handles": [
- "string"
], - "start_date": "2019-08-24",
- "end_date": "2019-08-24"
}{- "data": {
- "property1": {
- "affiliate_gmv": [
- 0
], - "video_gmv": [
- 0
], - "videos_posted": [
- 0
], - "total_video_views": [
- 0
], - "units_sold": [
- 0
], - "commission": [
- 0
]
}, - "property2": {
- "affiliate_gmv": [
- 0
], - "video_gmv": [
- 0
], - "videos_posted": [
- 0
], - "total_video_views": [
- 0
], - "units_sold": [
- 0
], - "commission": [
- 0
]
}
}, - "days": [
- "string"
], - "unresolved_handles": [
- "string"
], - "currency": "string",
- "date_range": {
- "start_date": "string",
- "end_date": "string"
}
}The outreach funnel — messages sent, sample requests, samples approved, videos posted — rolled up per creator GMV tier, one block per week.
Weeks run Sunday to Saturday and are labelled by the Saturday they end on (week_ending), matching how weekly client reports are cut. A week with no activity is still returned, as zeros.
All eight tiers L0-L7 are always present. A creator's tier comes from their ALL-TIME platform-wide GMV — the same basis as the Top Creators level badges — not from the GMV attributed inside the window. The gmv field on each row IS the windowed figure.
touches is outreach messages: three DMs to one creator counts three. reached is distinct creators. Single shop only — set x-shop-id to one shop ID.
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). Defaults to 8 weeks back. | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). Defaults to yesterday. |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24"
}{- "shop_id": 0,
- "weeks": [
- {
- "week_start": "string",
- "week_ending": "string",
- "total": {
- "creators": 0,
- "reached": 0,
- "converted": 0,
- "conversion_rate": 0,
- "touches": 0,
- "sample_requests": 0,
- "samples_approved": 0,
- "videos_posted": 0,
- "gmv": 0
}, - "tiers": [
- {
- "tier": "string",
- "creators": 0,
- "reached": 0,
- "converted": 0,
- "conversion_rate": 0,
- "touches": 0,
- "sample_requests": 0,
- "samples_approved": 0,
- "videos_posted": 0,
- "gmv": 0
}
]
}
]
}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.
Start Date (string) or Start Date (null) (Start Date) Start date (YYYY-MM-DD). Defaults to 30 days ago. | |
End Date (string) or End Date (null) (End Date) End date (YYYY-MM-DD). Defaults to the shop's newest settled day. |
nullGet detailed information for a single creator, including groups, products, and latest 10 videos (for full video list, use POST /creators/{creator_handle}/videos).
| creator_handle required | string (Creator Handle) |
{- "data": {
- "creator_handle": "string",
- "follower_count": 0,
- "status": "string",
- "shop_gmv": 0,
- "overall_gmv": 0,
- "creator_level": "string",
- "overall_fulfillment_rate": 0,
- "shop_units_sold": 0,
- "shop_video_count": 0,
- "video_views": 0,
- "sample_received": 0,
- "commission_rate": 0,
- "est_commission": 0,
- "fulfillment_rate": 0,
- "tags": [
- "string"
], - "product_id": "string",
- "product_title": "string",
- "updated_at": "string",
- "created_at": "string"
}, - "groups": [ ],
- "products": [ ],
- "videos": [ ],
- "currency": "string"
}Paginated list of videos by this creator in your shop.
| creator_handle required | string (Creator Handle) |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 50 |
Sort By (string) or Sort By (null) (Sort By) | |
| sort_dir | string (Sort Dir) Default: "desc" Enum: "asc" "desc" |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) |
{- "page": 1,
- "page_size": 50,
- "sort_by": "video_gmv",
- "sort_dir": "asc",
- "start_date": "2019-08-24",
- "end_date": "2019-08-24"
}{- "data": [
- {
- "video_id": "string",
- "creator_id": "string",
- "title": "string",
- "video_url": "string",
- "tiktok_url": "string",
- "creator_handle": "string",
- "product_id": "string",
- "product_name": "string",
- "views": 0,
- "like_count": 0,
- "comment_count": 0,
- "share_count": 0,
- "units_sold": 0,
- "video_gmv": 0,
- "posted_date": "string"
}
], - "currency": "string",
- "pagination": null,
- "date_range": {
- "start_date": "string",
- "end_date": "string"
}, - "shops_queried": [
- "string"
]
}Products this creator is affiliated with in your shop.
| creator_handle required | string (Creator Handle) |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 50 |
{- "page": 1,
- "page_size": 50
}{- "data": [
- {
- "product_id": "string",
- "product_title": "string",
- "commission_rate": 0,
- "shop_units_sold": 0,
- "shop_gmv": 0,
- "status": "string"
}
], - "currency": "string",
- "pagination": null,
- "shops_queried": [
- "string"
]
}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.
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 50 |
Sort By (string) or Sort By (null) (Sort By) | |
| sort_dir | string (Sort Dir) Default: "desc" Enum: "asc" "desc" |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) | |
Product Name (string) or Product Name (null) (Product Name) |
{- "page": 1,
- "page_size": 50,
- "sort_by": "gmv",
- "sort_dir": "asc",
- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "product_name": "string"
}{- "data": [
- {
- "product_id": "string",
- "product_name": "string",
- "gmv": 0,
- "units_sold": 0,
- "refund_units": 0,
- "video_count": 0,
- "sample_count": 0,
- "live_count": 0,
- "est_commission": 0,
- "product_status": "string",
- "sc_total_gmv": 0,
- "sc_orders": 0,
- "sc_units_sold": 0,
- "sc_customers": 0,
- "sc_aov": 0,
- "sc_impressions": 0,
- "sc_clicks": 0,
- "sc_ctr": 0,
- "sc_conversion": 0,
- "sc_gmv_per_1k_impr": 0,
- "sc_add_to_cart": 0,
- "sc_affiliate_gmv": 0,
- "sc_seller_video_gmv": 0,
- "sc_seller_live_gmv": 0,
- "sc_product_card_gmv": 0,
- "sc_shop_tab_gmv": 0,
- "sc_refunds": 0
}
], - "currency": "string",
- "pagination": null,
- "date_range": {
- "start_date": "string",
- "end_date": "string"
}, - "shops_queried": [
- "string"
]
}The shop's merchandising catalog with current stock — for syncing products into an external CRM or ordering system. Sourced from TikTok's product catalog (refreshed roughly every 6 hours), NOT the affiliate performance data in POST /products/list.
Each product carries its product-level inventory (total_inventory, sku_count, out_of_stock_sku_count) and, by default, a nested skus[] array with per-SKU stock and variant attributes. Set include_skus=false for a lighter product-only payload.
All money is reported as integer minor currency units (cents) in the product's currency. Prices and stock are passed through exactly as TikTok reports them: product-level total_inventory and per-SKU inventory can legitimately differ during a sync, so do not assume total_inventory equals the sum of SKU inventories. There is no warehouse-level stock breakdown — inventory exists only at the product and SKU grain.
For incremental syncs, pass updated_since and key off each row's updated_at. Soft-deleted products are excluded.
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 50 |
Sort By (string) or Sort By (null) (Sort By) | |
| sort_dir | string (Sort Dir) Default: "desc" Enum: "asc" "desc" |
Product Name (string) or Product Name (null) (Product Name) | |
Product Status (string) or Product Status (null) (Product Status) Exact match on product_status, e.g. 'ACTIVATE'. | |
| in_stock_only | boolean (In Stock Only) Default: false When true, only products with total_inventory > 0. |
Updated Since (string) or Updated Since (null) (Updated Since) Return only products Reacher synced on/after this instant. Accepts a plain date (YYYY-MM-DD, treated as midnight UTC) or a full ISO 8601 timestamp — pass the | |
| include_skus | boolean (Include Skus) Default: true Embed the per-SKU inventory array on each product. |
{- "page": 1,
- "page_size": 50,
- "sort_by": "title",
- "sort_dir": "asc",
- "product_name": "string",
- "product_status": "string",
- "in_stock_only": false,
- "updated_since": "2019-08-24T14:15:22Z",
- "include_skus": true
}{- "data": [
- {
- "shop_id": 0,
- "product_id": "string",
- "title": "string",
- "description": "string",
- "brand_name": "string",
- "category_chain": [
- null
], - "primary_image_url": "string",
- "image_urls": [
- null
], - "currency": "string",
- "price_min_cents": 0,
- "price_max_cents": 0,
- "total_inventory": 0,
- "sku_count": 0,
- "out_of_stock_sku_count": 0,
- "product_status": "string",
- "affiliate_status": 0,
- "is_cod_allowed": true,
- "tiktok_created_at": "string",
- "tiktok_updated_at": "string",
- "updated_at": "string",
- "skus": [
- {
- "sku_id": "string",
- "seller_sku": "string",
- "inventory": 0,
- "variants": [
- {
- "name": "string",
- "value_name": "string",
- "value_id": "string",
- "image_url": "string"
}
], - "updated_at": "string"
}
]
}
], - "pagination": null,
- "shops_queried": [
- "string"
]
}Creators affiliated with this product.
| product_id required | string (Product Id) |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 50 |
{- "page": 1,
- "page_size": 50
}{- "data": [
- {
- "creator_handle": "string",
- "follower_count": 0,
- "commission_rate": 0,
- "gmv": 0
}
], - "currency": "string",
- "pagination": null,
- "shops_queried": [
- "string"
]
}Videos featuring this product.
| product_id required | string (Product Id) |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 50 |
Sort By (string) or Sort By (null) (Sort By) | |
| sort_dir | string (Sort Dir) Default: "desc" Enum: "asc" "desc" |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) |
{- "page": 1,
- "page_size": 50,
- "sort_by": "video_gmv",
- "sort_dir": "asc",
- "start_date": "2019-08-24",
- "end_date": "2019-08-24"
}{- "data": [
- {
- "video_id": "string",
- "creator_id": "string",
- "title": "string",
- "video_url": "string",
- "tiktok_url": "string",
- "creator_handle": "string",
- "product_id": "string",
- "product_name": "string",
- "views": 0,
- "like_count": 0,
- "comment_count": 0,
- "share_count": 0,
- "units_sold": 0,
- "video_gmv": 0,
- "posted_date": "string"
}
], - "currency": "string",
- "pagination": null,
- "date_range": {
- "start_date": "string",
- "end_date": "string"
}, - "shops_queried": [
- "string"
]
}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.
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 50 |
Sort By (string) or Sort By (null) (Sort By) | |
| sort_dir | string (Sort Dir) Default: "desc" Enum: "asc" "desc" |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) | |
Creator Id (string) or Creator Id (null) (Creator Id) | |
Creator Handle (string) or Creator Handle (null) (Creator Handle) | |
Product Id (string) or Product Id (null) (Product Id) | |
Array of Product Ids (strings) or Product Ids (null) (Product Ids) Restrict to these product ids (IN filter). Max 50. The single product_id filter still works and is ANDed if both are supplied. | |
Min Gmv (number) or Min Gmv (null) (Min Gmv) | |
Min Views (integer) or Min Views (null) (Min Views) |
{- "page": 1,
- "page_size": 50,
- "sort_by": "video_gmv",
- "sort_dir": "asc",
- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "creator_id": "string",
- "creator_handle": "string",
- "product_id": "string",
- "product_ids": [
- "string"
], - "min_gmv": 0,
- "min_views": 0
}{- "data": [
- {
- "video_id": "string",
- "creator_id": "string",
- "title": "string",
- "video_url": "string",
- "tiktok_url": "string",
- "creator_handle": "string",
- "product_id": "string",
- "product_name": "string",
- "views": 0,
- "like_count": 0,
- "comment_count": 0,
- "share_count": 0,
- "units_sold": 0,
- "video_gmv": 0,
- "posted_date": "string"
}
], - "currency": "string",
- "pagination": null,
- "date_range": {
- "start_date": "string",
- "end_date": "string"
}, - "shops_queried": [
- "string"
]
}Creator-level shoppable-video leaderboard for a campaign window. Aggregates attributed video GMV and units per creator over a posted-date range (optionally restricted to specific product_ids), each with a per-product breakdown, ranked by total video GMV (default). Use this instead of paginating /videos/list + joining by hand: one row per creator with total_video_gmv, total_units_sold, qualifying_video_count (DISTINCT videos), and a products[] breakdown. min_gmv is a per-video-row threshold consistent with /videos/list. Multi-shop requests (x-shop-id: all or comma-separated) aggregate creators across shops, but only when every shop shares one currency — GMV cannot be summed across currencies, so a mixed-currency shop set is rejected (400); scope x-shop-id to a single currency/region.
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 50 |
Sort By (string) or Sort By (null) (Sort By) | |
| sort_dir | string (Sort Dir) Default: "desc" Enum: "asc" "desc" |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) | |
Array of Product Ids (strings) or Product Ids (null) (Product Ids) | |
Min Gmv (number) or Min Gmv (null) (Min Gmv) |
{- "page": 1,
- "page_size": 50,
- "sort_by": "total_video_gmv",
- "sort_dir": "asc",
- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "product_ids": [
- "string"
], - "min_gmv": 0
}{- "data": [
- {
- "creator_id": "string",
- "creator_handle": "string",
- "total_video_gmv": 0,
- "total_units_sold": 0,
- "qualifying_video_count": 0,
- "products": [
- {
- "product_id": "string",
- "product_name": "string",
- "video_gmv": 0,
- "units_sold": 0,
- "video_count": 0
}
]
}
], - "currency": "string",
- "pagination": null,
- "date_range": {
- "start_date": "string",
- "end_date": "string"
}, - "shops_queried": [
- "string"
]
}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.
| limit | integer (Limit) [ 1 .. 50 ] Default: 10 |
Sort By (string) or Sort By (null) (Sort By) | |
| sort_dir | string (Sort Dir) Default: "desc" Enum: "asc" "desc" |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) |
{- "limit": 10,
- "sort_by": "video_gmv",
- "sort_dir": "asc",
- "start_date": "2019-08-24",
- "end_date": "2019-08-24"
}{- "data": [
- {
- "video_id": "string",
- "title": "string",
- "creator_handle": "string",
- "tiktok_url": "string",
- "video_gmv": 0,
- "views": 0,
- "like_count": 0,
- "comment_count": 0,
- "order_count": 0,
- "posted_date": "string",
- "rank": 0,
- "creative": {
- "analyzed": true,
- "hook": {
- "text": "string",
- "classification": "string",
- "reasoning": "string"
}, - "sell_points": [
- "string"
], - "product_niche": "string",
- "shot_style": [
- "string"
], - "videography": {
- "locations": [
- "string"
], - "lighting": [
- "string"
], - "product_showcase": [
- "string"
], - "notes": "string"
}, - "missing_fields": [
- "string"
]
}
}
], - "currency": "string",
- "date_range": {
- "start_date": "string",
- "end_date": "string"
}, - "coverage": {
- "videos_returned": 0,
- "with_creative_analysis": 0
}, - "total_count": 0,
- "message": "string",
- "shops_queried": [
- "string"
]
}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.
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 50 |
Sort By (string) or Sort By (null) (Sort By) | |
| sort_dir | string (Sort Dir) Default: "desc" Enum: "asc" "desc" |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) | |
Creator Handle (string) or Creator Handle (null) (Creator Handle) | |
Min Gmv (number) or Min Gmv (null) (Min Gmv) | |
Min Views (integer) or Min Views (null) (Min Views) |
{- "page": 1,
- "page_size": 50,
- "sort_by": "video_gmv",
- "sort_dir": "asc",
- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "creator_handle": "string",
- "min_gmv": 0,
- "min_views": 0
}{- "data": [
- {
- "video_id": "string",
- "title": "string",
- "creator_handle": "string",
- "tiktok_url": "string",
- "video_gmv": 0,
- "views": 0,
- "like_count": 0,
- "comment_count": 0,
- "order_count": 0,
- "posted_date": "string"
}
], - "currency": "string",
- "pagination": null,
- "date_range": {
- "start_date": "string",
- "end_date": "string"
}, - "shops_queried": [
- "string"
]
}Your shop's videos with per-video completion rate (watch_finish_rate), exposure (impressions), views, CTR and engagement — the organic hook-quality signal.
TikTok exposes a 2-second view rate only for GMV Max ad creatives (video_view_rate_2s on /campaigns/{id}/creative-metrics); for organic videos the retention metric is completion, so use watch_finish_rate as the quality bar (min_watch_finish_rate filters to it). Ordered strongest-hook first.
Scope: videos in the shop's own Seller Center analytics (posted for the shop).
Creator Handle (string) or Creator Handle (null) (Creator Handle) Restrict to a creator by (partial) TikTok handle. | |
Min Watch Finish Rate (number) or Min Watch Finish Rate (null) (Min Watch Finish Rate) Only videos whose completion rate is at least this (percent). | |
Min Views (integer) or Min Views (null) (Min Views) Only videos with at least this many views. Pair with | |
| page | integer (Page) >= 1 Default: 1 Page number (1-indexed). |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 50 Rows per page (max 100). |
{- "data": [
- {
- "video_id": "string",
- "video_url": "string",
- "video_name": "string",
- "video_duration_s": 0,
- "publish_time": "string",
- "creator_handle": "string",
- "creator_alias": "string",
- "views": 0,
- "impressions": 0,
- "clicks": 0,
- "ctr_rate": 0,
- "watch_finish_rate": 0,
- "likes": 0,
- "comments": 0,
- "shares": 0,
- "new_followers": 0,
- "gpm": 0,
- "revenue": 0,
- "currency": "string",
- "products": [
- {
- "product_id": "string",
- "id": "string",
- "name": "string"
}
]
}
], - "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0
}
}Per-video transcripts and AI creative analysis: how a video opens, its structure, on-screen text, detected products, scored creative features, and its bottom-of-funnel verdict.
Reacher's AI analysis of a single video: the spoken transcript, a hook_summary of how it opens, the structure_outline of its beats, the on-screen text (OCR), the visual tags, the products detected on screen, and the scored creative features (hook in the first three seconds, product demo present, voiceover present, pace, and a content summary).
video_id is the value returned by POST /videos/list, POST /videos/performance and GET /crm/content.
Coverage is partial and grows over time: a video this shop owns that has not been analyzed yet returns 200 with analyzed: false and null fields, so absence is distinguishable from a bad id. Thumbnail-source analyses (analysis_source: thumbnail) carry no transcript and no motion features.
The analysis returned is of the creator's publicly-posted video content — public content anyone can watch, not another customer's data. Any analyzed video is readable here: one of your own shop's posts returns the full response; any other analyzed video returns the same public creative fields with no shop-derived metrics. The is_own_content flag says which (true = your own post, false = the creator's public content that is not one of your posts). A false value never means the video belongs to another customer, and no other shop's identity, metrics, or ownership is ever disclosed. Only a video_id with no analysis at all returns 404.
Set include_experimental=true to additionally receive features_experimental, the analyzer's full scored-feature block — richer, but unstable across analyzer versions.
Single-shop only — set x-shop-id to one shop ID rather than all or a comma-separated list.
| video_id required | string (Video Id) <= 255 characters Video id from any Reacher video surface. |
| include_experimental | boolean (Include Experimental) Default: false Attach the analyzer's full scored-feature block as |
{- "data": {
- "video_id": "string",
- "analyzed": true,
- "is_own_content": true,
- "analysis_source": "string",
- "analysis_version": 0,
- "analyzed_at": "2019-08-24T14:15:22Z",
- "legacy_judgement_fields": true,
- "transcript": "string",
- "transcript_language": "string",
- "transcript_duration_s": 0,
- "transcript_source": "string",
- "hook_summary": "string",
- "structure_outline": [
- "string"
], - "on_screen_text": "string",
- "vision_tags": { },
- "detected_products": { },
- "features": { },
- "features_experimental": { }
}
}The batch form of GET /videos/{video_id}/analysis — same payload per video, up to 100 ids per call. Pass video_ids once per id (?video_ids=A&video_ids=B).
Unlike the single-video route this never 404s: any analyzed video appears in data — the shop's own posts with is_own_content: true, any other analyzed video as the creator's public content with is_own_content: false (same cross-shop rules as the single-video route). Owned-but-unanalyzed videos appear with analyzed: false; not_found lists ids with no analysis at all. Use it to check analysis coverage across a set of videos in one round trip.
Single-shop only — set x-shop-id to one shop ID rather than all or a comma-separated list.
| video_ids required | Array of strings (Video Ids) Repeatable video id (max 100 per request). |
| include_experimental | boolean (Include Experimental) Default: false Attach the analyzer's full scored-feature block as |
{- "data": [
- {
- "video_id": "string",
- "analyzed": true,
- "is_own_content": true,
- "analysis_source": "string",
- "analysis_version": 0,
- "analyzed_at": "2019-08-24T14:15:22Z",
- "legacy_judgement_fields": true,
- "transcript": "string",
- "transcript_language": "string",
- "transcript_duration_s": 0,
- "transcript_source": "string",
- "hook_summary": "string",
- "structure_outline": [
- "string"
], - "on_screen_text": "string",
- "vision_tags": { },
- "detected_products": { },
- "features": { },
- "features_experimental": { }
}
], - "not_found": [
- "string"
]
}Whether a video is bottom-of-funnel content — direct-response creative that pushes the viewer to buy now (discount, urgency, explicit call to action) rather than building awareness.
bof_status is the raw verdict (bof, not_bof, needs_review, pending) and bof_confidence scores it 0-100; is_bof is the displayable answer, true only for a bof verdict at confidence >= 90 — the same threshold behind the BOF badge and filter in the Reacher portal, so the API and the portal can never disagree about a video. matched_keywords, matched_patterns and matched_signals explain the verdict.
A video this shop owns that the classifier has no verdict for returns 200 with classified: false and is_bof: false; classification_status then says why (pending, no_transcript, or not_analyzed) — classified: false is a coverage state, not an error.
Only videos belonging to the calling key's shop are readable; any other video_id returns 404. Single-shop only — set x-shop-id to one shop ID.
| video_id required | string (Video Id) <= 255 characters Video id from any Reacher video surface. |
{- "data": {
- "video_id": "string",
- "classified": true,
- "classification_status": "classified",
- "bof_status": "string",
- "bof_confidence": 0,
- "is_bof": true,
- "matched_signals": [
- "string"
], - "matched_keywords": [
- "string"
], - "matched_patterns": [
- "string"
], - "classified_at": "2019-08-24T14:15:22Z"
}
}Track sample requests from creators in your shop. Shows only sample-related statuses. Data updates approximately every hour.
Each 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.
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 50 |
Sort By (string) or Sort By (null) (Sort By) | |
| sort_dir | string (Sort Dir) Default: "desc" Enum: "asc" "desc" |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) | |
Status (string) or Status (null) (Status) Filter by status. Accepts the labels returned in | |
Creator Handle (string) or Creator Handle (null) (Creator Handle) | |
Product Id (string) or Product Id (null) (Product Id) |
{- "page": 1,
- "page_size": 50,
- "sort_by": "updated_at",
- "sort_dir": "asc",
- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "status": "string",
- "creator_handle": "string",
- "product_id": "string"
}{- "data": [
- {
- "creator_handle": "string",
- "product_id": "string",
- "product_title": "string",
- "status": "string",
- "gmv": 0,
- "units_sold": 0,
- "sample_received": 0,
- "updated_at": "string",
- "bio": "string",
- "categories": [
- "string"
], - "email": "string"
}
], - "currency": "string",
- "pagination": null,
- "date_range": {
- "start_date": "string",
- "end_date": "string"
}, - "shops_queried": [
- "string"
]
}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.
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 50 |
Sort By (string) or Sort By (null) (Sort By) | |
| sort_dir | string (Sort Dir) Default: "desc" Enum: "asc" "desc" |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) |
{- "page": 1,
- "page_size": 50,
- "sort_by": "total_requests",
- "sort_dir": "asc",
- "start_date": "2019-08-24",
- "end_date": "2019-08-24"
}{- "data": [
- {
- "product_id": "string",
- "product_name": "string",
- "total_requests": 0,
- "approved": 0,
- "sample_gmv": 0,
- "videos_from_samples": 0
}
], - "currency": "string",
- "pagination": null,
- "date_range": {
- "start_date": "string",
- "end_date": "string"
}, - "shops_queried": [
- "string"
]
}The four stat cards above the sample-request table: pendingReview (how many requests await a decision, plus how many of those are expiring soon), approvedInAutoProcess (samples auto-approved by an automation), completionRate (share of approved samples that produced content), and approvalTarget (usage against the shop's approval quota). The change metrics compare against the immediately preceding period of equal length.
dataFreshness reports how current the underlying sample data is — TikTok's sample feed lags, so surface it rather than presenting the counts as real-time.
Under a sku_ids filter, notScopableBySku names the cards that could not be narrowed — approvedInAutoProcess and approvalTarget, whose counters are product-grain — so treat those as unscoped rather than filtered.
date_from/date_to must be given together and bound request creation date. Single-shop only.
Product Ids (string) or Product Ids (null) (Product Ids) Comma-separated product IDs to filter by. Omit for all products. | |
Sku Ids (string) or Sku Ids (null) (Sku Ids) Comma-separated SKU IDs. AND-ed with product_ids, so passing both narrows to those SKUs rather than widening. | |
Date From (string) or Date From (null) (Date From) Start of the window (inclusive). Requires date_to. | |
Date To (string) or Date To (null) (Date To) End of the window (inclusive). Requires date_from. | |
| exclude_rejected | boolean (Exclude Rejected) Default: false Exclude creators previously rejected/cancelled by this shop. |
| include_archived | boolean (Include Archived) Default: false Include sample requests archived in the portal. |
{- "pendingReview": {
- "count": 0,
- "expiringSoon": 0
}, - "approvedInAutoProcess": {
- "count": 0,
- "changePct": 0,
- "changeDirection": "up"
}, - "completionRate": {
- "rate": 0,
- "changePct": 0,
- "changeDirection": "up"
}, - "approvalTarget": {
- "monthlyLimit": 0,
- "monthlyUsed": 0,
- "dailyTarget": 0,
- "dailyCurrent": 0,
- "dailyPct": 0,
- "onTrack": true
}, - "dataFreshness": {
- "lastSyncedAt": "string",
- "isStale": false,
- "isRefreshing": false
}, - "notScopableBySku": [
- "string"
]
}How many sample requests sit at each stage of the fulfilment funnel: toReview → readyToShip → shipped → pendingContent → completed, plus cancelled. Also returns the shop's completionRate, its approvalTarget usage, the archivedCount, and expiringCount (requests about to lapse if no decision is made). approvalTarget is always shop-wide: it ignores product_ids and sku_ids.
Counts honour the same filters as the portal table, so a filtered funnel and a filtered list agree. They EXCLUDE requests hidden because the creator is archived (archivedCount.total is how many those are, and archivedCounts is unset here); for stage counts that include them, and so match TikTok's Affiliate Center, use GET /funnel/v2/counts. The portal's advanced numeric creator filters (GMV / views / post-rate bands) are not exposed here — use POST /samples/list and aggregate if you need those.
date_from/date_to must be given together. Single-shop only.
Product Ids (string) or Product Ids (null) (Product Ids) Comma-separated product IDs to filter by. | |
Sku Ids (string) or Sku Ids (null) (Sku Ids) Comma-separated SKU IDs. AND-ed with product_ids, so passing both narrows to those SKUs rather than widening. | |
Date From (string) or Date From (null) (Date From) Start of the window (inclusive). Requires date_to. | |
Date To (string) or Date To (null) (Date To) End of the window (inclusive). Requires date_from. | |
| exclude_rejected | boolean (Exclude Rejected) Default: false Exclude creators previously rejected/cancelled by this shop. |
| include_archived | boolean (Include Archived) Default: false Include sample requests archived in the portal. |
Search (string) or Search (null) (Search) Free-text filter on creator handle / name. | |
Tags (string) or Tags (null) (Tags) Comma-separated CRM tags to filter by. | |
| show_expiring | boolean (Show Expiring) Default: false Count only requests that are close to expiring. |
{- "counts": {
- "toReview": 0,
- "readyToShip": 0,
- "shipped": 0,
- "pendingContent": 0,
- "completed": 0,
- "cancelled": 0,
- "all": 0,
- "inShipment": 0,
- "posted": 0,
- "allApproved": 0,
- "overdue": 0,
- "rejected": 0,
- "expired": 0,
- "ignored": 0
}, - "archivedCounts": {
- "toReview": 0,
- "readyToShip": 0,
- "shipped": 0,
- "pendingContent": 0,
- "completed": 0,
- "cancelled": 0,
- "all": 0,
- "inShipment": 0,
- "posted": 0,
- "allApproved": 0,
- "overdue": 0,
- "rejected": 0,
- "expired": 0,
- "ignored": 0
}, - "completionRate": 0,
- "approvalTarget": {
- "limit": 0,
- "used": 0
}, - "archivedCount": {
- "creators": 0,
- "products": 0,
- "total": 0
}, - "expiringCount": 0,
- "dataFreshness": {
- "lastSyncedAt": "string",
- "isStale": false,
- "isRefreshing": false
}
}How many samples each product has consumed in the current calendar month (productId → usedThisMonth). This is the counter the portal checks per-product sample limits against, so it answers 'which product is about to run out of its monthly allowance'.
Products with no usage this month are omitted. Single-shop only.
{- "data": [
- {
- "productId": "string",
- "usedThisMonth": 0
}
]
}Per-product sample auto-approval rules for this shop: monthly sample limit, daily approval target, the minimum GMV / post-rate a requester must clear, the ideal-creator profiles they must match, and how many samples of that product have already gone out this month (usedThisMonth). Products with no row here follow the shop-wide quota only. Read-only; requires a single shop.
{- "data": [
- {
- "id": 0,
- "productId": "string",
- "productName": "string",
- "sku": "string",
- "productImage": "string",
- "monthlySampleLimit": 0,
- "dailyApprovalTarget": 0,
- "usedThisMonth": 0,
- "idealCreatorProfiles": [
- {
- "id": 0,
- "name": "string"
}
], - "config": {
- "minGmv": 0,
- "minPostRate": 0
}, - "eligibleSkuIds": [
- "string"
], - "ineligibleSkuMessage": "string",
- "createdAt": "string",
- "updatedAt": "string"
}
]
}Create the auto-approval rule for one product. Requires read_write scope and a single shop. productId must not already have a config in this shop — a duplicate returns 409 PRODUCT_CONFIG_EXISTS; use PUT /samples/product-config/{config_id} to change an existing rule. idealCreatorProfileIds that don't belong to this shop are dropped rather than linked.
Monthlysamplelimit (integer) or Monthlysamplelimit (null) (Monthlysamplelimit) | |
Dailyapprovaltarget (integer) or Dailyapprovaltarget (null) (Dailyapprovaltarget) | |
Array of Idealcreatorprofileids (integers) or Idealcreatorprofileids (null) (Idealcreatorprofileids) | |
ProductConfigMinCriteria (object) or null | |
Array of Eligibleskuids (strings) or Eligibleskuids (null) (Eligibleskuids) | |
Ineligibleskumessage (string) or Ineligibleskumessage (null) (Ineligibleskumessage) | |
| productId required | string (Productid) [ 1 .. 255 ] characters |
{- "monthlySampleLimit": 0,
- "dailyApprovalTarget": 0,
- "idealCreatorProfileIds": [
- 0
], - "config": {
- "minGmv": 0,
- "minPostRate": 0
}, - "eligibleSkuIds": [
- "string"
], - "ineligibleSkuMessage": "string",
- "productId": "string"
}{- "success": true,
- "data": {
- "id": 0,
- "productId": "string",
- "productName": "string",
- "sku": "string",
- "productImage": "string",
- "monthlySampleLimit": 0,
- "dailyApprovalTarget": 0,
- "usedThisMonth": 0,
- "idealCreatorProfiles": [
- {
- "id": 0,
- "name": "string"
}
], - "config": {
- "minGmv": 0,
- "minPostRate": 0
}, - "eligibleSkuIds": [
- "string"
], - "ineligibleSkuMessage": "string",
- "createdAt": "string",
- "updatedAt": "string"
}
}Partially update one product's auto-approval rule. Requires read_write scope and a single shop. Omitted fields keep their current value; an explicit null clears the setting. config_id is re-checked against this shop before the write — a config belonging to another shop returns 404, never a cross-shop update.
| config_id required | integer (Config Id) |
Monthlysamplelimit (integer) or Monthlysamplelimit (null) (Monthlysamplelimit) | |
Dailyapprovaltarget (integer) or Dailyapprovaltarget (null) (Dailyapprovaltarget) | |
Array of Idealcreatorprofileids (integers) or Idealcreatorprofileids (null) (Idealcreatorprofileids) | |
ProductConfigMinCriteria (object) or null | |
Array of Eligibleskuids (strings) or Eligibleskuids (null) (Eligibleskuids) | |
Ineligibleskumessage (string) or Ineligibleskumessage (null) (Ineligibleskumessage) |
{- "monthlySampleLimit": 0,
- "dailyApprovalTarget": 0,
- "idealCreatorProfileIds": [
- 0
], - "config": {
- "minGmv": 0,
- "minPostRate": 0
}, - "eligibleSkuIds": [
- "string"
], - "ineligibleSkuMessage": "string"
}{- "success": true,
- "data": {
- "id": 0,
- "productId": "string",
- "productName": "string",
- "sku": "string",
- "productImage": "string",
- "monthlySampleLimit": 0,
- "dailyApprovalTarget": 0,
- "usedThisMonth": 0,
- "idealCreatorProfiles": [
- {
- "id": 0,
- "name": "string"
}
], - "config": {
- "minGmv": 0,
- "minPostRate": 0
}, - "eligibleSkuIds": [
- "string"
], - "ineligibleSkuMessage": "string",
- "createdAt": "string",
- "updatedAt": "string"
}
}Remove one product's auto-approval rule. The product falls back to the shop-wide quota; nothing already approved is affected. Requires read_write scope and a single shop. config_id is re-checked against this shop before the delete — a foreign ID returns 404.
| config_id required | integer (Config Id) |
{- "success": true
}The shop-wide monthly sample ceiling. monthlyLimit is null when the shop has never set one, which means unlimited — per-product limits still apply. Read-only; requires a single shop.
{- "success": true,
- "data": {
- "shopId": 0,
- "monthlyLimit": 0,
- "createdAt": "string",
- "updatedAt": "string"
}
}Set the shop-wide monthly sample ceiling. Requires read_write scope and a single shop. monthlyLimit must be present: a number caps the shop, null removes the cap. The shop is taken from the authenticated key / x-shop-id, never from the body.
Monthlylimit (integer) or Monthlylimit (null) (Monthlylimit) |
{- "monthlyLimit": 0
}{- "success": true,
- "data": {
- "shopId": 0,
- "monthlyLimit": 0,
- "createdAt": "string",
- "updatedAt": "string"
}
}Reusable creator-quality criteria (minimum post rate, GMV range, minimum views, minimum followers) that product configs point at when deciding whether to auto-approve a sample request. linkedProducts counts how many product configs reference each profile. Read-only; requires a single shop.
{- "data": [
- {
- "id": 0,
- "name": "string",
- "criteria": {
- "minPostRate": 0,
- "gmvRange": {
- "min": 0,
- "max": 0
}, - "minViews": 0,
- "minFollowers": 0
}, - "linkedProducts": 0,
- "createdAt": "string",
- "updatedAt": "string"
}
]
}Create a reusable ideal-creator profile for this shop. Requires read_write scope and a single shop. Names are unique per shop — a duplicate returns 409 PROFILE_NAME_EXISTS. Link the new profile to products via idealCreatorProfileIds on a product config.
| name required | string (Name) [ 1 .. 255 ] characters |
required | object (ProfileCriteria) |
{- "name": "string",
- "criteria": {
- "minPostRate": 0,
- "gmvRange": {
- "min": 0,
- "max": 0
}, - "minViews": 0,
- "minFollowers": 0
}
}{- "success": true,
- "data": {
- "id": 0,
- "name": "string",
- "criteria": {
- "minPostRate": 0,
- "gmvRange": {
- "min": 0,
- "max": 0
}, - "minViews": 0,
- "minFollowers": 0
}, - "linkedProducts": 0,
- "createdAt": "string",
- "updatedAt": "string"
}
}Replace a profile's name and criteria. Requires read_write scope and a single shop. profile_id is re-checked against this shop before the write — a profile belonging to another shop returns 404. Renaming onto an existing name returns 409 PROFILE_NAME_EXISTS. Product configs linked to this profile pick up the new criteria immediately.
| profile_id required | integer (Profile Id) |
| name required | string (Name) [ 1 .. 255 ] characters |
required | object (ProfileCriteria) |
{- "name": "string",
- "criteria": {
- "minPostRate": 0,
- "gmvRange": {
- "min": 0,
- "max": 0
}, - "minViews": 0,
- "minFollowers": 0
}
}{- "success": true,
- "data": {
- "id": 0,
- "name": "string",
- "criteria": {
- "minPostRate": 0,
- "gmvRange": {
- "min": 0,
- "max": 0
}, - "minViews": 0,
- "minFollowers": 0
}, - "linkedProducts": 0,
- "createdAt": "string",
- "updatedAt": "string"
}
}Delete a profile and unlink it from every product config that referenced it — those products keep their other criteria. Requires read_write scope and a single shop. profile_id is re-checked against this shop before the delete — a foreign ID returns 404.
| profile_id required | integer (Profile Id) |
{- "success": true
}Internal-only creator sample-review intelligence: a creator's recent videos with AI creative analysis, per-video metrics, and the creator aggregate a reviewer weighs before approving a sample request. Access restricted to internal Reacher staff keys; not available to customer API keys.
For deciding a creator's sample request: the creator's top videos over a recent window, each with the AI creative analysis, the internal metrics (GMV, views, engagement), plus a creator aggregate (lifetime GMV, video/live counts, followers, current status, samples already received, tags).
Videos are ranked by rank_by (gmv or views) over the last window_days and capped at limit. A video the pipeline has not analyzed yet still appears with its metrics and uvi_analyzed: false. Set include_full_features=true to also receive the full scored-feature block per video.
Recommended fields to weigh (also returned as field_guide):
hook_summary — How the video opens — the first seconds decide whether viewers stay, so a weak hook predicts weak reach.transcript — What the creator actually says — check the product claims are accurate and the messaging fits the brand.on_screen_text — On-screen captions/text (OCR) — catches claims, prices, or disclaimers shown but not spoken.structure_outline — The beats of the video (hook, demo, CTA) — a clear structure signals a creator who can sell, not just post.detected_products — Products the analyzer saw on screen — confirms the creator actually featured the product, i.e. product fit.vision_tags — Visual tags for setting, style, and content type — a fast read on production quality and brand-safety.features.hook_present_first_3s — Whether a real hook lands in the first three seconds — the single strongest retention signal.features.has_product_demo — Whether the product is actually demonstrated — demos convert far better than mentions.features.solution_shown — Whether the video shows the product solving a problem — the core of a persuasive sample video.features.has_voiceover — Whether the creator narrates — voiceover content tends to feel more authentic and engaged.features.video_pace — Editing pace — matches (or clashes with) the brand's desired energy and the platform's norms.features.content_summary — A one-line summary of the video — the quickest gist before opening the full analysis.metrics.views / metrics.video_gmv — Actual reach and sales this creator's recent content drove — the outcome the sample is betting on.creator.sample_received_num / creator.curr_status — How many samples this creator already got and where they stand — flags repeat requesters and stalled relationships.Shop Id (integer) or Shop Id (null) (Shop Id) Optional. The shop is taken from the request's shop (x-shop-id). If given, shop_id must equal that shop or the call is rejected; omit it to use the shop already set on the request. | |
| creator_id required | string (Creator Id) [ 1 .. 255 ] characters Creator id, scoped to the shop. |
| rank_by | string (Rank By) Default: "gmv" Enum: "gmv" "views" Rank the creator's videos by lifetime GMV or lifetime views. |
| window_days | integer (Window Days) [ 1 .. 365 ] Default: 30 Only videos posted within this many days back are considered. |
| limit | integer (Limit) [ 1 .. 50 ] Default: 10 Number of top videos to return (1-50). |
| include_full_features | boolean (Include Full Features) Default: false When true, also return the full scored-feature block per video ( |
{- "shop_id": 0,
- "creator_id": "string",
- "rank_by": "gmv",
- "window_days": 30,
- "limit": 10,
- "include_full_features": false
}{- "shop_id": 0,
- "currency_code": "string",
- "rank_by": "gmv",
- "window_days": 0,
- "creator": {
- "creator_id": "string",
- "creator_name": "string",
- "gmv": 0,
- "video_num": 0,
- "live_num": 0,
- "follower_num": 0,
- "curr_status": "string",
- "sample_received_num": 0,
- "tags": [
- "string"
], - "crm_found": true
}, - "videos": [
- {
- "video_id": "string",
- "creator_id": "string",
- "creator_name": "string",
- "posted_date": "string",
- "is_shoppable": true,
- "metrics": {
- "video_gmv": 0,
- "shoppable_gmv": 0,
- "official_gmv": 0,
- "views": 0,
- "units_sold": 0,
- "like_count": 0,
- "comment_count": 0,
- "share_count": 0
}, - "uvi_analyzed": true,
- "analyzed_at": "string",
- "analysis_version": 0,
- "uvi_recommended": {
- "hook_summary": "string",
- "transcript": "string",
- "transcript_source": "string",
- "on_screen_text": "string",
- "structure_outline": [
- "string"
], - "detected_products": { },
- "vision_tags": { },
- "features": { }
}, - "uvi_full_features": { }
}
], - "field_guide": [
- {
- "field": "string",
- "why": "string"
}
]
}The full per-video composite for a single video: identity, internal metrics (GMV, views, engagement), and the UVI creative analysis (hook, transcript, on-screen text, structure, detected products, vision tags, and scored features).
A video the shop owns but the pipeline has not analyzed yet returns with uvi_analyzed: false; a video the shop does not own returns 404. Set include_full_features=true to also receive the full scored-feature block.
Recommended fields to weigh (also returned as field_guide):
hook_summary — How the video opens — the first seconds decide whether viewers stay, so a weak hook predicts weak reach.transcript — What the creator actually says — check the product claims are accurate and the messaging fits the brand.on_screen_text — On-screen captions/text (OCR) — catches claims, prices, or disclaimers shown but not spoken.structure_outline — The beats of the video (hook, demo, CTA) — a clear structure signals a creator who can sell, not just post.detected_products — Products the analyzer saw on screen — confirms the creator actually featured the product, i.e. product fit.vision_tags — Visual tags for setting, style, and content type — a fast read on production quality and brand-safety.features.hook_present_first_3s — Whether a real hook lands in the first three seconds — the single strongest retention signal.features.has_product_demo — Whether the product is actually demonstrated — demos convert far better than mentions.features.solution_shown — Whether the video shows the product solving a problem — the core of a persuasive sample video.features.has_voiceover — Whether the creator narrates — voiceover content tends to feel more authentic and engaged.features.video_pace — Editing pace — matches (or clashes with) the brand's desired energy and the platform's norms.features.content_summary — A one-line summary of the video — the quickest gist before opening the full analysis.metrics.views / metrics.video_gmv — Actual reach and sales this creator's recent content drove — the outcome the sample is betting on.creator.sample_received_num / creator.curr_status — How many samples this creator already got and where they stand — flags repeat requesters and stalled relationships.Shop Id (integer) or Shop Id (null) (Shop Id) Optional. The shop is taken from the request's shop (x-shop-id). If given, shop_id must equal that shop or the call is rejected; omit it to use the shop already set on the request. | |
| video_id required | string (Video Id) [ 1 .. 255 ] characters Video id, scoped to the shop. |
| include_full_features | boolean (Include Full Features) Default: false When true, also return the full scored-feature block ( |
{- "shop_id": 0,
- "video_id": "string",
- "include_full_features": false
}{- "shop_id": 0,
- "currency_code": "string",
- "data": {
- "video_id": "string",
- "creator_id": "string",
- "creator_name": "string",
- "posted_date": "string",
- "is_shoppable": true,
- "metrics": {
- "video_gmv": 0,
- "shoppable_gmv": 0,
- "official_gmv": 0,
- "views": 0,
- "units_sold": 0,
- "like_count": 0,
- "comment_count": 0,
- "share_count": 0
}, - "uvi_analyzed": true,
- "analyzed_at": "string",
- "analysis_version": 0,
- "uvi_recommended": {
- "hook_summary": "string",
- "transcript": "string",
- "transcript_source": "string",
- "on_screen_text": "string",
- "structure_outline": [
- "string"
], - "detected_products": { },
- "vision_tags": { },
- "features": { }
}, - "uvi_full_features": { }
}, - "field_guide": [
- {
- "field": "string",
- "why": "string"
}
]
}Automation performance and outreach metrics. Email automations are a separate kind with their own read + lifecycle routes under /automations/email (they are not returned by POST /automations/list).
Shop-level overview of your automations: total_automations, live_automations, archived_automations, and the split between regular_automations and email_automations, plus three breakdowns — by_type (Target Collab, Message, Email, Sample Request Processing, …), by_status (running / stopped / completed / …) and by_type_and_state (each type split live vs archived) — and an aggregated metrics block rolled up across the shop's automations.
This is the portfolio view; use POST /automations/list for the per-automation rows behind it. Pass crm_only=true to count only automations backed by a CRM group. Single-shop only.
| crm_only | boolean (Crm Only) Default: false Count only CRM-backed automations (those with a crm_group_id). Defaults to false — all automations. |
{- "total_automations": 0,
- "live_automations": 0,
- "archived_automations": 0,
- "email_automations": 0,
- "regular_automations": 0,
- "by_type": {
- "property1": 0,
- "property2": 0
}, - "by_status": {
- "property1": 0,
- "property2": 0
}, - "by_type_and_state": {
- "property1": {
- "property1": 0,
- "property2": 0
}, - "property2": {
- "property1": 0,
- "property2": 0
}
}, - "metrics": {
- "total_creators_reached": 0,
- "total_sample_requests": 0,
- "total_creators": 0,
- "total_emails_sent": 0,
- "total_dm_messages_sent": 0,
- "total_dm_response_count": 0,
- "total_reply_rate": 0,
- "total_email_open_count": 0,
- "total_email_reply_count": 0,
- "total_email_open_rate": 0,
- "total_email_reply_rate": 0
}
}The precomputed performance rollup for ONE automation — the numbers the portal shows above the automation's tabs.
tab_counts carries the creator-tracker, sample-request and video counts (the tracker count already excludes creators on the automation's current omit lists, so it matches the tab it labels). top_line_metrics carries creators_reached, dm_response_count and reply_rate, unique_creators_posted and post_rate, videos_count, total_revenue (GMV), and total views / shares / likes.
Check data_available first. It is false when the rollup aggregator has not yet computed a row for this automation — the zeros are then 'not computed yet', not 'nothing happened'. top_line_metrics is null for automation types that have no full-funnel view (is_full_view: false).
Single-shop only; the automation must belong to that shop or this returns 404.
| automation_id required | integer (Automation Id) |
{- "data_available": true,
- "is_full_view": false,
- "tab_counts": {
- "creator_tracker_count": 0,
- "sample_request_count": 0,
- "video_count": 0,
- "reply_count": 0,
- "unread_count": 0
}, - "top_line_metrics": {
- "creators_reached": 0,
- "dm_response_count": 0,
- "reply_rate": 0,
- "unique_creators_posted": 0,
- "post_rate": 0,
- "videos_count": 0,
- "total_revenue": 0,
- "total_views": 0,
- "total_shares": 0,
- "total_likes": 0
}
}Your email automations, newest first. Email automations are a separate kind from outreach automations and are NOT returned by POST /automations/list; this is where they live. Archived automations are excluded unless include_archived=true. automation_status is one of: inactive, running, paused, failed, stopped, archived.
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 50 |
| include_archived | boolean (Include Archived) Default: false |
{- "data": [
- {
- "automation_id": 0,
- "shop_id": 0,
- "automation_name": "string",
- "automation_type": "target_collab",
- "automation_status": "string",
- "state": "string",
- "config": {
- "sample_request_config": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}, - "sample_approval": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}
}, - "schedule": { },
- "creators_to_include": { },
- "creators_to_exclude": { },
- "crm_group_id": "string",
- "ai_enabled": false,
- "end_date": "string",
- "created_at": "string",
- "updated_at": "string",
- "created_via": "string",
- "side_effects": { }
}
], - "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0
}, - "shops_queried": [
- "string"
]
}Create an outbound email automation. Different recipient model from outreach — use selection_mode: mailing_list (with vault list IDs or upload emails inline), selection_mode: filters (creator search criteria), or selection_mode: crm_stage (target a CRM/funnel stage — the server resolves the stage's creators to their email addresses). For crm_stage, creators with no email on file are skipped, and the response reports the reachable audience versus the stage size under config.recipient_coverage; a stage that resolves to no reachable emails is rejected. An email_account_id may back several active email automations. Each one adds to that inbox's daily send volume, and the daily cap is applied per automation, so heavy reuse risks provider throttling or spam classification.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) | |
X-Created-Via (string) or X-Created-Via (null) (X-Created-Via) |
| automation_name required | string (Automation Name) [ 1 .. 120 ] characters |
| email_account_id required | integer (Email Account Id) > 0 Pre-connected email account. May back several active email automations; each one adds to that inbox's daily send volume. |
| subject required | string (Subject) [ 1 .. 255 ] characters |
| body required | string (Body) [ 1 .. 50000 ] characters HTML or plaintext. |
| selection_mode required | string (Selection Mode) Enum: "mailing_list" "filters" "crm_stage" |
EmailMailingList (object) or null | |
EmailFilters (object) or null | |
EmailCrmStage (object) or null | |
object (CreatorsToExclude) Outreach-paradigm recipient exclusion. Mirrors | |
Array of objects (Attachments) <= 10 items Email attachments — | |
Email Sent Per Day (integer) or Email Sent Per Day (null) (Email Sent Per Day) Maximum emails this automation sends per day from its connected inbox. Applied per automation, so multiple automations on the same inbox each get their own daily budget. Omit to use the default of 40 emails per day. |
{- "automation_name": "string",
- "email_account_id": 0,
- "subject": "string",
- "body": "string",
- "selection_mode": "mailing_list",
- "mailing_list": {
- "list_upload_emails": [
- "string"
], - "lists_selected": [
- "string"
]
}, - "filters": {
- "min_creator_gmv": 0,
- "min_followers": 0,
- "min_engagement_rate": 1,
- "min_avg_views": 0,
- "regions": [
- "string"
], - "categories": [
- "string"
]
}, - "crm_stage": {
- "stage": "string"
}, - "creators_to_exclude": {
- "list_upload": [
- "string"
], - "lists_selected": [
- "string"
], - "crm_groups": [
- "string"
], - "exclude_previously_messaged": false
}, - "attachments": [
- {
- "name": "string",
- "url": "string"
}
], - "email_sent_per_day": 1
}{- "data": {
- "automation_id": 0,
- "shop_id": 0,
- "automation_name": "string",
- "automation_type": "target_collab",
- "automation_status": "string",
- "state": "string",
- "config": {
- "sample_request_config": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}, - "sample_approval": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}
}, - "schedule": { },
- "creators_to_include": { },
- "creators_to_exclude": { },
- "crm_group_id": "string",
- "ai_enabled": false,
- "end_date": "string",
- "created_at": "string",
- "updated_at": "string",
- "created_via": "string",
- "side_effects": { }
}, - "dry_run": false,
- "side_effects": { }
}One email automation, including its subject/body and daily send cap.
| email_automation_id required | integer (Email Automation Id) >= 1 |
{- "data": {
- "automation_id": 0,
- "shop_id": 0,
- "automation_name": "string",
- "automation_type": "target_collab",
- "automation_status": "string",
- "state": "string",
- "config": {
- "sample_request_config": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}, - "sample_approval": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}
}, - "schedule": { },
- "creators_to_include": { },
- "creators_to_exclude": { },
- "crm_group_id": "string",
- "ai_enabled": false,
- "end_date": "string",
- "created_at": "string",
- "updated_at": "string",
- "created_via": "string",
- "side_effects": { }
}
}Soft-delete (archive) an email automation. It stops sending and is removed from the default list. Requires read_write scope and an Idempotency-Key; X-Dry-Run: true previews without writing.
| email_automation_id required | integer (Email Automation Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
{- "data": {
- "automation_id": 0,
- "shop_id": 0,
- "automation_name": "string",
- "automation_type": "target_collab",
- "automation_status": "string",
- "state": "string",
- "config": {
- "sample_request_config": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}, - "sample_approval": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}
}, - "schedule": { },
- "creators_to_include": { },
- "creators_to_exclude": { },
- "crm_group_id": "string",
- "ai_enabled": false,
- "end_date": "string",
- "created_at": "string",
- "updated_at": "string",
- "created_via": "string",
- "side_effects": { }
}, - "dry_run": false,
- "side_effects": { }
}Partial update of an email automation: automation_name, subject, body, and/or email_sent_per_day. Supply only what changes. The recipient audience is not editable here. Requires read_write scope; Idempotency-Key optional; X-Dry-Run: true previews without writing.
| email_automation_id required | integer (Email Automation Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
Automation Name (string) or Automation Name (null) (Automation Name) | |
Subject (string) or Subject (null) (Subject) | |
Body (string) or Body (null) (Body) HTML or plaintext. | |
Email Sent Per Day (integer) or Email Sent Per Day (null) (Email Sent Per Day) Maximum emails per day this automation sends from its inbox. |
{- "automation_name": "string",
- "subject": "string",
- "body": "string",
- "email_sent_per_day": 1
}{- "data": {
- "automation_id": 0,
- "shop_id": 0,
- "automation_name": "string",
- "automation_type": "target_collab",
- "automation_status": "string",
- "state": "string",
- "config": {
- "sample_request_config": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}, - "sample_approval": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}
}, - "schedule": { },
- "creators_to_include": { },
- "creators_to_exclude": { },
- "crm_group_id": "string",
- "ai_enabled": false,
- "end_date": "string",
- "created_at": "string",
- "updated_at": "string",
- "created_via": "string",
- "side_effects": { }
}, - "dry_run": false,
- "side_effects": { }
}Stop an email automation so it stops sending. Requires read_write scope and an Idempotency-Key; X-Dry-Run: true previews without writing.
| email_automation_id required | integer (Email Automation Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
{- "data": {
- "automation_id": 0,
- "shop_id": 0,
- "automation_name": "string",
- "automation_type": "target_collab",
- "automation_status": "string",
- "state": "string",
- "config": {
- "sample_request_config": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}, - "sample_approval": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}
}, - "schedule": { },
- "creators_to_include": { },
- "creators_to_exclude": { },
- "crm_group_id": "string",
- "ai_enabled": false,
- "end_date": "string",
- "created_at": "string",
- "updated_at": "string",
- "created_via": "string",
- "side_effects": { }
}, - "dry_run": false,
- "side_effects": { }
}Start an email automation so it resumes sending on the next send cycle. Note: unlike the portal, this does not pre-validate the inbox connection — a disconnected inbox surfaces as an authentication error on the first send attempt. Requires read_write scope and an Idempotency-Key; X-Dry-Run: true previews without writing.
| email_automation_id required | integer (Email Automation Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
{- "data": {
- "automation_id": 0,
- "shop_id": 0,
- "automation_name": "string",
- "automation_type": "target_collab",
- "automation_status": "string",
- "state": "string",
- "config": {
- "sample_request_config": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}, - "sample_approval": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}
}, - "schedule": { },
- "creators_to_include": { },
- "creators_to_exclude": { },
- "crm_group_id": "string",
- "ai_enabled": false,
- "end_date": "string",
- "created_at": "string",
- "updated_at": "string",
- "created_via": "string",
- "side_effects": { }
}, - "dry_run": false,
- "side_effects": { }
}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.
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 50 |
Sort By (string) or Sort By (null) (Sort By) | |
| sort_dir | string (Sort Dir) Default: "desc" Enum: "asc" "desc" |
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). Scopes ACTIVITY, not creation: when supplied (alone or with | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). When supplied (alone or with | |
Automation Type (string) or Automation Type (null) (Automation Type) Filter by the raw | |
Status (string) or Status (null) (Status) Filter by status. Accepts either the display status ( | |
| show_archived | boolean (Show Archived) Default: false Include archived automations (state=archived). Default false mirrors the portal; the |
Array of Created Via (strings) or Created Via (null) (Created Via) Include only automations created by these surfaces. Common values: | |
Array of Exclude Created Via (strings) or Exclude Created Via (null) (Exclude Created Via) Exclude automations created by these surfaces. Applied after |
{- "page": 1,
- "page_size": 50,
- "sort_by": "gmv",
- "sort_dir": "asc",
- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "automation_type": "string",
- "status": "string",
- "show_archived": false,
- "created_via": [
- "string"
], - "exclude_created_via": [
- "string"
]
}{- "data": [
- {
- "automation_id": 0,
- "automation_name": "string",
- "automation_type": "string",
- "is_evergreen": false,
- "status": "string",
- "status_message": "string",
- "gmv": 0,
- "sample_requests": 0,
- "accepted_requests": 0,
- "videos_posted": 0,
- "videos_converted": 0,
- "creators_reached": 0,
- "target_collab_cleanup_creators_reached": 0,
- "dm_response_count": 0,
- "reply_rate": 0,
- "skipped": 0,
- "total_creators": 0,
- "creators_remaining": 0,
- "created_at": "string",
- "completed_at": "string",
- "created_via": "string"
}
], - "currency": "string",
- "pagination": null,
- "date_range": {
- "start_date": "string",
- "end_date": "string"
}, - "shops_queried": [
- "string"
], - "aggregate": {
- "creators_reached": 0,
- "creators_reached_sum_of_rows": 0,
- "tc_invites": 0,
- "tc_invites_creator_count": 0,
- "creators_messaged": 0,
- "emails_sent": 0,
- "sample_request": 0,
- "sample_approved": 0,
- "videos_posted": 0,
- "videos_converted": 0,
- "spark_codes": 0,
- "added_showcase_affiliates": 0,
- "accepted_tc_count": 0,
- "tc_showcase_creator_count": 0,
- "tc_content_creator_count": 0,
- "tc_acceptance_rate": 0,
- "coverage_through": "string"
}, - "excluded": { }
}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.
Region differences:
£ for UK, $ for US, € for EU regions, R$ for BR, etc.).Supported regions: US, UK, IE, MX, DE, FR, ES, BR, IT, GLOBAL (alias for US).
| shop_region required | string (Shop Region) |
nullCurrent outreach send-capacity for a single shop, so callers can pre-check before sending TC invites / DMs and schedule retries when capacity frees.
Two independent limits govern outreach:
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.outreach_quota for the consumption, ceiling and refresh instant. It counts connections shop-wide, so it includes outreach not sent through Reacher. Most shops are on a weekly PERIOD quota; some hold a BONUS grant that does not roll, so check outreach_quota.quota_type before calling it weekly. Null when the shop has no synced quota — null means unknown, never zero. weekly_limit_throttled flags when paused by the limit.The daily pool is shared between the automation engine and direct POST /target-collabs invites. Requires a single shop.
{- "shop_id": 0,
- "daily_creators_used": 0,
- "daily_window_resets_at": "string",
- "concurrent_automations_running": 0,
- "max_concurrent_automations": 0,
- "daily_limit_throttled": true,
- "weekly_limit_throttled": true,
- "outreach_quota": {
- "quota_type": "string",
- "used": 0,
- "limit": 0,
- "remaining": 0,
- "unlimited": true,
- "resets_at": "string",
- "gmv_tier": "string",
- "gmv_level_rule": [
- null
]
}
}Detailed automation data with performance metrics.
| automation_id required | integer (Automation Id) |
{- "data": {
- "automation_id": 0,
- "automation_name": "string",
- "automation_type": "string",
- "is_evergreen": false,
- "status": "string",
- "status_message": "string",
- "gmv": 0,
- "sample_requests": 0,
- "accepted_requests": 0,
- "videos_posted": 0,
- "videos_converted": 0,
- "creators_reached": 0,
- "target_collab_cleanup_creators_reached": 0,
- "dm_response_count": 0,
- "reply_rate": 0,
- "skipped": 0,
- "total_creators": 0,
- "creators_remaining": 0,
- "created_at": "string",
- "completed_at": "string",
- "created_via": "string"
}, - "creators_remaining": 0,
- "details": {
- "messaging": {
- "creator_message": "string",
- "message_type": "string",
- "message_sequence": [
- {
- "message_type": "string",
- "message_entry": "string",
- "product_id": "string",
- "days_after_previous": 0,
- "has_image": true
}
], - "follow_up_steps": [
- {
- "message_type": "string",
- "message_entry": "string",
- "product_id": "string",
- "days_after_previous": 0,
- "has_image": true
}
], - "personalization": { },
- "ai_custom_qa": [
- null
], - "spark_code_form": { }
}, - "target_collab": {
- "invitation_name": "string",
- "invitation_message": "string",
- "valid_until": "string",
- "content_type": "string",
- "products": [
- {
- "product_id": "string",
- "product_name": "string",
- "commission_rate": 0,
- "shop_min_commission": 0,
- "shop_ads_commission_rate": 0
}
], - "offer_free_samples": true,
- "auto_approve_samples": true,
- "auto_resolve_conflicts": "string",
- "support_contact_email": "string",
- "support_contact_phone": "string",
- "invite_start_before_days": "string",
- "invite_expire_after_days": "string"
}, - "sample_request": {
- "min_shop_gmv": 0
}, - "schedule": {
- "daily_caps": {
- "property1": 0,
- "property2": 0
}, - "start_time": "string",
- "timezone": "string"
}, - "targeting": {
- "creator_source_type": "string",
- "include_lists": [
- "string"
], - "include_uploads": [
- null
], - "crm_group_id": "string",
- "exclude_lists": [
- "string"
], - "exclude_crm_groups": [
- null
], - "exclude_previously_messaged": true,
- "filters": { },
- "ai_search_filters": { }
}
}, - "currency": "string"
}Partial update across all 4 types. Cannot change automation_type, shop, lifecycle state, or system fields. Idempotency-Key optional; X-Dry-Run: true supported.
| automation_id required | integer (Automation Id) |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
Automation Name (string) or Automation Name (null) (Automation Name) | |
AutomationSchedule (object) or null | |
CreatorsToInclude (object) or null | |
CreatorsToExclude (object) or null | |
Array of Messages (objects) or Messages (null) (Messages) Replace the initial-outreach addon set. ALL items are sent immediately together when the automation engages a creator. Pass | |
Array of Follow Ups (objects) or Follow Ups (null) (Follow Ups) Replace the delayed follow-up sequence. Pass | |
TargetCollabPatchPayload (object) or null Partial update for the TC-specific config. Send only the keys you want to change — supported sub-keys mirror | |
DmConfigPatchPayload (object) or null Partial update for the DM-specific config. Currently supports | |
SampleRequestPatchPayload (object) or null Partial update for the sample-request config. Send only the keys you want to change — supported sub-keys mirror | |
Ai Enabled (boolean) or Ai Enabled (null) (Ai Enabled) | |
Is Evergreen (boolean) or Is Evergreen (null) (Is Evergreen) | |
End Date (string) or End Date (null) (End Date) | |
Business Hours Timezone (string) or Business Hours Timezone (null) (Business Hours Timezone) Set or change the 9am–9pm send-window timezone (CORE-3031). Pass an IANA name to enable, or the empty string | |
Email Sent Per Day (integer) or Email Sent Per Day (null) (Email Sent Per Day) | |
Subject (string) or Subject (null) (Subject) | |
Body (string) or Body (null) (Body) |
{- "automation_name": "string",
- "schedule": {
- "Monday_maxCreators": 0,
- "Tuesday_maxCreators": 0,
- "Wednesday_maxCreators": 0,
- "Thursday_maxCreators": 0,
- "Friday_maxCreators": 0,
- "Saturday_maxCreators": 0,
- "Sunday_maxCreators": 0,
- "start_time": "string",
- "end_time": "string",
- "timezone": "string"
}, - "creators_to_include": {
- "list_upload": [
- "string"
], - "lists_selected": [
- "string"
], - "filters": {
- "product_categories": [
- "string"
], - "follower_count": {
- "min": 0,
- "max": 0
}, - "creator_ethnicity": [
- "string"
], - "gender": [
- "string"
], - "age_range": [
- "string"
], - "languages": [
- "string"
], - "community": [
- "string"
], - "gmv": {
- "min": 0,
- "max": 0
}, - "units_sold": {
- "min": 0,
- "max": 0
}, - "average_views": {
- "min": 0,
- "max": 0
}, - "engagement_rate": {
- "min": 0,
- "max": 0
}, - "post_rate": {
- "min": 0,
- "max": 0
}, - "video_gpm": {
- "min": 0,
- "max": 0
}, - "live_gpm": {
- "min": 0,
- "max": 0
}, - "follower_age": [
- "string"
], - "follower_gender": [
- "string"
], - "face_visibility": [
- "string"
], - "content_style": [
- "string"
], - "production_quality": [
- "string"
], - "personality_tone": [
- "string"
], - "body_type": [
- "string"
], - "hair_type": [
- "string"
]
}, - "crm_group_id": "string",
- "crm_group_entered_after": "2019-08-24",
- "crm_group_entered_before": "2019-08-24"
}, - "creators_to_exclude": {
- "list_upload": [
- "string"
], - "lists_selected": [
- "string"
], - "crm_groups": [
- "string"
], - "exclude_previously_messaged": false
}, - "messages": [
- {
- "type": "message",
- "body": "string",
- "image_url": "string",
- "product_id": "string",
- "submission_url_slug": "string"
}
], - "follow_ups": [
- {
- "delay_days": 30,
- "addons": [
- {
- "type": "message",
- "body": "string",
- "image_url": "string",
- "product_id": "string",
- "submission_url_slug": "string"
}
]
}
], - "target_collab": {
- "invitation_name": "string",
- "valid_until": "2019-08-24",
- "message": "string",
- "products": [
- {
- "product_id": "string",
- "commission_rate": 1,
- "shop_min_commission": 1,
- "shop_ads_commission_rate": 1
}
], - "content_type": "no_preference",
- "sample_policy": {
- "offer_free_samples": true,
- "auto_approve": true,
- "send_to": "brand_recipient"
}, - "support_contact": {
- "email": "string",
- "phone": "string"
}, - "lock_receivers_profiles": true
}, - "dm_config": {
- "spark_code": {
- "only_collect_creator_information": false,
- "spark_code_submission_url": "string"
}
}, - "sample_request": {
- "action": "approve",
- "rejection_reason": "NOT_MATCH",
- "approval_message": "string",
- "rejection_message": "string",
- "message_images": [
- "string"
], - "products": {
- "include_all_products": false,
- "selected_products": [
- {
- "product_id": "string",
- "max_approvals_per_week": 10000
}
], - "weekly_auto_approval_per_product": 1
}, - "minimum_performance_criteria": {
- "followers": 0,
- "avg_views": 0,
- "engagement_rate": 1,
- "post_rate": 1,
- "creator_gmv_30_days": 0,
- "gmv_per_sample": 0,
- "shop_gmv": 0
}, - "maximum_performance_criteria": {
- "followers": 0,
- "avg_views": 0,
- "engagement_rate": 1,
- "post_rate": 1,
- "creator_gmv_30_days": 0,
- "gmv_per_sample": 0
}, - "auto_approval_settings": {
- "enabled": false,
- "total_weekly_approved_sample_limit": 1,
- "always_approve_status": "string",
- "always_approve_from_lists_ids": [
- 0
]
}, - "target_list": [
- 0
]
}, - "ai_enabled": true,
- "is_evergreen": true,
- "end_date": "2019-08-24",
- "business_hours_timezone": "string",
- "email_sent_per_day": 1,
- "subject": "string",
- "body": "string"
}{- "data": {
- "automation_id": 0,
- "shop_id": 0,
- "automation_name": "string",
- "automation_type": "target_collab",
- "automation_status": "string",
- "state": "string",
- "config": {
- "sample_request_config": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}, - "sample_approval": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}
}, - "schedule": { },
- "creators_to_include": { },
- "creators_to_exclude": { },
- "crm_group_id": "string",
- "ai_enabled": false,
- "end_date": "string",
- "created_at": "string",
- "updated_at": "string",
- "created_via": "string",
- "side_effects": { }
}, - "dry_run": false,
- "side_effects": { }
}Soft-delete (state=archived). Reversible only by Reacher support. Calling on already-archived returns 200 with already_archived: true.
| automation_id required | integer (Automation Id) |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
{- "data": {
- "automation_id": 0,
- "shop_id": 0,
- "automation_name": "string",
- "automation_type": "target_collab",
- "automation_status": "string",
- "state": "string",
- "config": {
- "sample_request_config": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}, - "sample_approval": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}
}, - "schedule": { },
- "creators_to_include": { },
- "creators_to_exclude": { },
- "crm_group_id": "string",
- "ai_enabled": false,
- "end_date": "string",
- "created_at": "string",
- "updated_at": "string",
- "created_via": "string",
- "side_effects": { }
}, - "dry_run": false,
- "side_effects": { }
}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).
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.
| automation_id required | integer (Automation Id) |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 500 ] Default: 100 |
{- "data": [
- {
- "creator_handle": "string",
- "outcome": "string",
- "status": "string",
- "detail": "string",
- "skip_reason": "string",
- "invitation_id": "string"
}
], - "summary": {
- "total": 0,
- "invite_submitted": 0,
- "outreach_sent": 0,
- "previously_invited": 0,
- "invite_removed": 0,
- "skipped": 0,
- "unknown": 0
}, - "pagination": null
}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.
| automation_id required | integer (Automation Id) |
{- "automation_id": 0,
- "automation_name": "string",
- "automation_type": "string",
- "tc_invite_message": "string",
- "message_sequence": [
- null
], - "follow_up_steps": [
- null
], - "ai_custom_qa": [
- null
], - "personalization": {
- "enabled": false,
- "tone": "engaging",
- "fallback_message": "string",
- "offer_discussion": "string",
- "product_description": "string",
- "product_selling_points": "string",
- "word_counts": {
- "property1": 0,
- "property2": 0
}, - "name_source": "string"
}
}What this automation actually sent, with honest coverage semantics:
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.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.Hybrid automations (Message + TC) populate both sections. Empty sections carry an explicit message instead of blank lists. Read-only. Requires a single shop.
| automation_id required | integer (Automation Id) |
| creator_limit | integer (Creator Limit) [ 1 .. 100 ] Default: 20 |
| messages_per_creator | integer (Messages Per Creator) [ 1 .. 50 ] Default: 20 |
Creator Handle (string) or Creator Handle (null) (Creator Handle) |
{- "automation_id": 0,
- "automation_name": "string",
- "automation_type": "string",
- "template": {
- "automation_id": 0,
- "automation_name": "string",
- "automation_type": "string",
- "tc_invite_message": "string",
- "message_sequence": [
- null
], - "follow_up_steps": [
- null
], - "ai_custom_qa": [
- null
], - "personalization": {
- "enabled": false,
- "tone": "engaging",
- "fallback_message": "string",
- "offer_discussion": "string",
- "product_description": "string",
- "product_selling_points": "string",
- "word_counts": {
- "property1": 0,
- "property2": 0
}, - "name_source": "string"
}
}, - "dm_sent": {
- "exact_copy": true,
- "groups": [
- {
- "creator_name": "string",
- "creator_id": "string",
- "last_sent_at": "string",
- "messages": [
- {
- "content": "string",
- "sent_at": "string",
- "status": "string"
}
]
}
], - "creators_returned": 0,
- "total_creators": 0,
- "total_messages": 0,
- "message": "string"
}, - "tc_sent": {
- "exact_copy": false,
- "coverage_note": "string",
- "template_text": "string",
- "placeholders_present": false,
- "invitations": [
- {
- "sent_at": "string",
- "status": "string",
- "creator_count": 0,
- "creators": [
- "string"
], - "creators_truncated": false,
- "expiration_date": "string"
}
], - "invitations_returned": 0,
- "total_invitations": 0,
- "total_creators_invited": 0,
- "status_breakdown": {
- "property1": 0,
- "property2": 0
}, - "campaign": {
- "campaign_id": 0,
- "campaign_name": "string"
}, - "message": "string"
}, - "message": "string"
}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.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) | |
X-Created-Via (string) or X-Created-Via (null) (X-Created-Via) |
| automation_name required | string (Automation Name) [ 1 .. 120 ] characters |
required | object (AutomationSchedule) Per-day creator-outreach caps + run window. Stored into Automations.schedule JSONB. Same shape as the existing portal payload — keeps the portal and Public API agreeing on schema. |
object (CreatorsToInclude) Outreach-paradigm recipient inclusion. Three mutually-exclusive modes (cf. INTERNAL-design-review.md §11A):
The validator below rejects any request that mixes modes. The
| |
object (CreatorsToExclude) Outreach-paradigm recipient exclusion. Mirrors | |
required | object (TargetCollabConfig) TC-specific config — invitation metadata, commission rates, sample policy, content type, support contact. Maps to the legacy TC config blob shape (UPPERCASE root keys: BASE_INVITATION_NAME, VALID_UNTIL, TARGET_COLLAB_MESSAGE, EMAIL, PHONE_NUMBER, OFFER_FREE_SAMPLES, AUTO_APPROVE, PRODUCTS) — the worker reads from those, so the translator flattens this nested model accordingly. |
Array of objects (Messages) <= 5 items Initial-outreach addons — text, images, product cards, etc. ALL items in this list are sent immediately together when the automation engages a creator. Use | |
Array of objects (Follow Ups) <= 3 items Delayed follow-up steps. Each step has | |
| ai_enabled | boolean (Ai Enabled) Default: false |
End Date (string) or End Date (null) (End Date) | |
| auto_resolve_conflicts | string (Auto Resolve Conflicts) Default: "MOVE_NOT_ACCEPTED" Enum: "SKIP_ALL" "MOVE_NOT_ACCEPTED" "MOVE_ALL" 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 |
| is_evergreen | boolean (Is Evergreen) Default: false When true, the automation continuously re-evaluates its |
Business Hours Timezone (string) or Business Hours Timezone (null) (Business Hours Timezone) When set, the runtime restricts outbound message delivery (initial outreach + follow-ups) to 9am–9pm in this IANA timezone (e.g. |
{- "automation_name": "string",
- "schedule": {
- "Monday_maxCreators": 0,
- "Tuesday_maxCreators": 0,
- "Wednesday_maxCreators": 0,
- "Thursday_maxCreators": 0,
- "Friday_maxCreators": 0,
- "Saturday_maxCreators": 0,
- "Sunday_maxCreators": 0,
- "start_time": "string",
- "end_time": "string",
- "timezone": "string"
}, - "creators_to_include": {
- "list_upload": [
- "string"
], - "lists_selected": [
- "string"
], - "filters": {
- "product_categories": [
- "string"
], - "follower_count": {
- "min": 0,
- "max": 0
}, - "creator_ethnicity": [
- "string"
], - "gender": [
- "string"
], - "age_range": [
- "string"
], - "languages": [
- "string"
], - "community": [
- "string"
], - "gmv": {
- "min": 0,
- "max": 0
}, - "units_sold": {
- "min": 0,
- "max": 0
}, - "average_views": {
- "min": 0,
- "max": 0
}, - "engagement_rate": {
- "min": 0,
- "max": 0
}, - "post_rate": {
- "min": 0,
- "max": 0
}, - "video_gpm": {
- "min": 0,
- "max": 0
}, - "live_gpm": {
- "min": 0,
- "max": 0
}, - "follower_age": [
- "string"
], - "follower_gender": [
- "string"
], - "face_visibility": [
- "string"
], - "content_style": [
- "string"
], - "production_quality": [
- "string"
], - "personality_tone": [
- "string"
], - "body_type": [
- "string"
], - "hair_type": [
- "string"
]
}, - "crm_group_id": "string",
- "crm_group_entered_after": "2019-08-24",
- "crm_group_entered_before": "2019-08-24"
}, - "creators_to_exclude": {
- "list_upload": [
- "string"
], - "lists_selected": [
- "string"
], - "crm_groups": [
- "string"
], - "exclude_previously_messaged": false
}, - "target_collab": {
- "invitation_name": "string",
- "valid_until": "2019-08-24",
- "message": "string",
- "products": [
- {
- "product_id": "string",
- "commission_rate": 1,
- "shop_min_commission": 1,
- "shop_ads_commission_rate": 1
}
], - "content_type": "no_preference",
- "sample_policy": {
- "offer_free_samples": false,
- "auto_approve": false,
- "send_to": "brand_recipient"
}, - "support_contact": {
- "email": "string",
- "phone": "string"
}
}, - "messages": [
- {
- "type": "message",
- "body": "string",
- "image_url": "string",
- "product_id": "string",
- "submission_url_slug": "string"
}
], - "follow_ups": [
- {
- "delay_days": 30,
- "addons": [
- {
- "type": "message",
- "body": "string",
- "image_url": "string",
- "product_id": "string",
- "submission_url_slug": "string"
}
]
}
], - "ai_enabled": false,
- "end_date": "2019-08-24",
- "auto_resolve_conflicts": "SKIP_ALL",
- "is_evergreen": false,
- "business_hours_timezone": "string"
}{- "data": {
- "automation_id": 0,
- "shop_id": 0,
- "automation_name": "string",
- "automation_type": "target_collab",
- "automation_status": "string",
- "state": "string",
- "config": {
- "sample_request_config": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}, - "sample_approval": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}
}, - "schedule": { },
- "creators_to_include": { },
- "creators_to_exclude": { },
- "crm_group_id": "string",
- "ai_enabled": false,
- "end_date": "string",
- "created_at": "string",
- "updated_at": "string",
- "created_via": "string",
- "side_effects": { }
}, - "dry_run": false,
- "side_effects": { }
}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.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) | |
X-Created-Via (string) or X-Created-Via (null) (X-Created-Via) |
| automation_name required | string (Automation Name) [ 1 .. 120 ] characters |
required | object (AutomationSchedule) Per-day creator-outreach caps + run window. Stored into Automations.schedule JSONB. Same shape as the existing portal payload — keeps the portal and Public API agreeing on schema. |
object (CreatorsToInclude) Outreach-paradigm recipient inclusion. Three mutually-exclusive modes (cf. INTERNAL-design-review.md §11A):
The validator below rejects any request that mixes modes. The
| |
object (CreatorsToExclude) Outreach-paradigm recipient exclusion. Mirrors | |
required | Array of objects (Products) [ 1 .. 50 ] items 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 (integer) or Invite Start Before Days (null) (Invite Start Before Days) Re-target only creators whose original TC invite was sent at least N days ago. Null = no lower bound on invite age. Persists as | |
Invite Expire After Days (integer) or Invite Expire After Days (null) (Invite Expire After Days) Re-target only creators whose original TC invite expires within the next N days. Null = no upper bound on time-to-expiry. Persists as | |
End Date (string) or End Date (null) (End Date) |
{- "automation_name": "string",
- "schedule": {
- "Monday_maxCreators": 0,
- "Tuesday_maxCreators": 0,
- "Wednesday_maxCreators": 0,
- "Thursday_maxCreators": 0,
- "Friday_maxCreators": 0,
- "Saturday_maxCreators": 0,
- "Sunday_maxCreators": 0,
- "start_time": "string",
- "end_time": "string",
- "timezone": "string"
}, - "creators_to_include": {
- "list_upload": [
- "string"
], - "lists_selected": [
- "string"
], - "filters": {
- "product_categories": [
- "string"
], - "follower_count": {
- "min": 0,
- "max": 0
}, - "creator_ethnicity": [
- "string"
], - "gender": [
- "string"
], - "age_range": [
- "string"
], - "languages": [
- "string"
], - "community": [
- "string"
], - "gmv": {
- "min": 0,
- "max": 0
}, - "units_sold": {
- "min": 0,
- "max": 0
}, - "average_views": {
- "min": 0,
- "max": 0
}, - "engagement_rate": {
- "min": 0,
- "max": 0
}, - "post_rate": {
- "min": 0,
- "max": 0
}, - "video_gpm": {
- "min": 0,
- "max": 0
}, - "live_gpm": {
- "min": 0,
- "max": 0
}, - "follower_age": [
- "string"
], - "follower_gender": [
- "string"
], - "face_visibility": [
- "string"
], - "content_style": [
- "string"
], - "production_quality": [
- "string"
], - "personality_tone": [
- "string"
], - "body_type": [
- "string"
], - "hair_type": [
- "string"
]
}, - "crm_group_id": "string",
- "crm_group_entered_after": "2019-08-24",
- "crm_group_entered_before": "2019-08-24"
}, - "creators_to_exclude": {
- "list_upload": [
- "string"
], - "lists_selected": [
- "string"
], - "crm_groups": [
- "string"
], - "exclude_previously_messaged": false
}, - "products": [
- {
- "product_id": "string",
- "commission_rate": 1,
- "shop_min_commission": 1,
- "shop_ads_commission_rate": 1
}
], - "invite_start_before_days": 1,
- "invite_expire_after_days": 1,
- "end_date": "2019-08-24"
}{- "data": {
- "automation_id": 0,
- "shop_id": 0,
- "automation_name": "string",
- "automation_type": "target_collab",
- "automation_status": "string",
- "state": "string",
- "config": {
- "sample_request_config": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}, - "sample_approval": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}
}, - "schedule": { },
- "creators_to_include": { },
- "creators_to_exclude": { },
- "crm_group_id": "string",
- "ai_enabled": false,
- "end_date": "string",
- "created_at": "string",
- "updated_at": "string",
- "created_via": "string",
- "side_effects": { }
}, - "dry_run": false,
- "side_effects": { }
}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.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) | |
X-Created-Via (string) or X-Created-Via (null) (X-Created-Via) |
| automation_name required | string (Automation Name) [ 1 .. 120 ] characters |
| mode | string (Mode) Default: "vanilla" Enum: "vanilla" "with_image" "with_product_card" "spark_code" |
required | object (AutomationSchedule) Per-day creator-outreach caps + run window. Stored into Automations.schedule JSONB. Same shape as the existing portal payload — keeps the portal and Public API agreeing on schema. |
object (CreatorsToInclude) Outreach-paradigm recipient inclusion. Three mutually-exclusive modes (cf. INTERNAL-design-review.md §11A):
The validator below rejects any request that mixes modes. The
| |
object (CreatorsToExclude) Outreach-paradigm recipient exclusion. Mirrors | |
required | Array of objects (Messages) [ 1 .. 5 ] items Initial-outreach addons — text + optional image / product card / spark_code form. ALL items are sent immediately together when the automation engages a creator. Use |
Array of objects (Follow Ups) <= 3 items Delayed follow-up steps — same shape as TC. | |
DmConfig (object) or null | |
DmPersonalizationConfig (object) or null 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 | |
| ai_enabled | boolean (Ai Enabled) Default: false |
End Date (string) or End Date (null) (End Date) | |
| is_evergreen | boolean (Is Evergreen) Default: false When true, the automation continuously re-evaluates its |
Business Hours Timezone (string) or Business Hours Timezone (null) (Business Hours Timezone) When set, the runtime restricts outbound message delivery (initial outreach + follow-ups) to 9am–9pm in this IANA timezone (e.g. |
{- "automation_name": "string",
- "mode": "vanilla",
- "schedule": {
- "Monday_maxCreators": 0,
- "Tuesday_maxCreators": 0,
- "Wednesday_maxCreators": 0,
- "Thursday_maxCreators": 0,
- "Friday_maxCreators": 0,
- "Saturday_maxCreators": 0,
- "Sunday_maxCreators": 0,
- "start_time": "string",
- "end_time": "string",
- "timezone": "string"
}, - "creators_to_include": {
- "list_upload": [
- "string"
], - "lists_selected": [
- "string"
], - "filters": {
- "product_categories": [
- "string"
], - "follower_count": {
- "min": 0,
- "max": 0
}, - "creator_ethnicity": [
- "string"
], - "gender": [
- "string"
], - "age_range": [
- "string"
], - "languages": [
- "string"
], - "community": [
- "string"
], - "gmv": {
- "min": 0,
- "max": 0
}, - "units_sold": {
- "min": 0,
- "max": 0
}, - "average_views": {
- "min": 0,
- "max": 0
}, - "engagement_rate": {
- "min": 0,
- "max": 0
}, - "post_rate": {
- "min": 0,
- "max": 0
}, - "video_gpm": {
- "min": 0,
- "max": 0
}, - "live_gpm": {
- "min": 0,
- "max": 0
}, - "follower_age": [
- "string"
], - "follower_gender": [
- "string"
], - "face_visibility": [
- "string"
], - "content_style": [
- "string"
], - "production_quality": [
- "string"
], - "personality_tone": [
- "string"
], - "body_type": [
- "string"
], - "hair_type": [
- "string"
]
}, - "crm_group_id": "string",
- "crm_group_entered_after": "2019-08-24",
- "crm_group_entered_before": "2019-08-24"
}, - "creators_to_exclude": {
- "list_upload": [
- "string"
], - "lists_selected": [
- "string"
], - "crm_groups": [
- "string"
], - "exclude_previously_messaged": false
}, - "messages": [
- {
- "type": "message",
- "body": "string",
- "image_url": "string",
- "product_id": "string",
- "submission_url_slug": "string"
}
], - "follow_ups": [
- {
- "delay_days": 30,
- "addons": [
- {
- "type": "message",
- "body": "string",
- "image_url": "string",
- "product_id": "string",
- "submission_url_slug": "string"
}
]
}
], - "dm_config": {
- "spark_code": {
- "only_collect_creator_information": false,
- "spark_code_submission_url": "string"
}
}, - "personalization": {
- "enabled": true,
- "tone": "engaging",
- "fallback_message": "string",
- "offer_discussion": "",
- "product_description": "",
- "product_selling_points": "",
- "word_counts": {
- "property1": 0,
- "property2": 0
}, - "name_source": "first_name"
}, - "ai_enabled": false,
- "end_date": "2019-08-24",
- "is_evergreen": false,
- "business_hours_timezone": "string"
}{- "data": {
- "automation_id": 0,
- "shop_id": 0,
- "automation_name": "string",
- "automation_type": "target_collab",
- "automation_status": "string",
- "state": "string",
- "config": {
- "sample_request_config": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}, - "sample_approval": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}
}, - "schedule": { },
- "creators_to_include": { },
- "creators_to_exclude": { },
- "crm_group_id": "string",
- "ai_enabled": false,
- "end_date": "string",
- "created_at": "string",
- "updated_at": "string",
- "created_via": "string",
- "side_effects": { }
}, - "dry_run": false,
- "side_effects": { }
}Create a copy of an existing automation you own — same audience, config, and schedule — as a NEW automation in the STOPPED state. The copy never sends until it is explicitly started in the portal. Requires read_write scope and an Idempotency-Key.
| automation_id required | integer (Automation Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Created-Via (string) or X-Created-Via (null) (X-Created-Via) |
{- "data": {
- "automation_id": 0,
- "shop_id": 0,
- "automation_name": "string",
- "automation_type": "target_collab",
- "automation_status": "string",
- "state": "string",
- "config": {
- "sample_request_config": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}, - "sample_approval": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}
}, - "schedule": { },
- "creators_to_include": { },
- "creators_to_exclude": { },
- "crm_group_id": "string",
- "ai_enabled": false,
- "end_date": "string",
- "created_at": "string",
- "updated_at": "string",
- "created_via": "string",
- "side_effects": { }
}, - "dry_run": false,
- "side_effects": { }
}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.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) | |
X-Created-Via (string) or X-Created-Via (null) (X-Created-Via) |
| automation_name required | string (Automation Name) [ 1 .. 120 ] characters |
required | object (SampleRequestPayload) Inner sample_request config — mirrors the legacy
|
AutomationSchedule (object) or null Optional — sample_request can run continuously. If set, the automation only auto-approves/rejects during scheduled hours. | |
object (CreatorsToExclude) Optional creator exclusion. Persists as |
{- "automation_name": "string",
- "sample_request": {
- "action": "approve",
- "rejection_reason": "NOT_MATCH",
- "approval_message": "string",
- "rejection_message": "string",
- "message_images": [
- "string"
], - "products": {
- "include_all_products": false,
- "selected_products": [
- {
- "product_id": "string",
- "max_approvals_per_week": 10000
}
], - "weekly_auto_approval_per_product": 1
}, - "minimum_performance_criteria": {
- "followers": 0,
- "avg_views": 0,
- "engagement_rate": 1,
- "post_rate": 1,
- "creator_gmv_30_days": 0,
- "gmv_per_sample": 0,
- "shop_gmv": 0
}, - "maximum_performance_criteria": {
- "followers": 0,
- "avg_views": 0,
- "engagement_rate": 1,
- "post_rate": 1,
- "creator_gmv_30_days": 0,
- "gmv_per_sample": 0
}, - "auto_approval_settings": {
- "enabled": false,
- "total_weekly_approved_sample_limit": 1,
- "always_approve_status": "string",
- "always_approve_from_lists_ids": [
- 0
]
}, - "target_list": [
- 0
]
}, - "schedule": {
- "Monday_maxCreators": 0,
- "Tuesday_maxCreators": 0,
- "Wednesday_maxCreators": 0,
- "Thursday_maxCreators": 0,
- "Friday_maxCreators": 0,
- "Saturday_maxCreators": 0,
- "Sunday_maxCreators": 0,
- "start_time": "string",
- "end_time": "string",
- "timezone": "string"
}, - "creators_to_exclude": {
- "list_upload": [
- "string"
], - "lists_selected": [
- "string"
], - "crm_groups": [
- "string"
], - "exclude_previously_messaged": false
}
}{- "data": {
- "automation_id": 0,
- "shop_id": 0,
- "automation_name": "string",
- "automation_type": "target_collab",
- "automation_status": "string",
- "state": "string",
- "config": {
- "sample_request_config": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}, - "sample_approval": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}
}, - "schedule": { },
- "creators_to_include": { },
- "creators_to_exclude": { },
- "crm_group_id": "string",
- "ai_enabled": false,
- "end_date": "string",
- "created_at": "string",
- "updated_at": "string",
- "created_via": "string",
- "side_effects": { }
}, - "dry_run": false,
- "side_effects": { }
}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.
| automation_id required | integer (Automation Id) |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
{- "data": {
- "automation_id": 0,
- "shop_id": 0,
- "automation_name": "string",
- "automation_type": "target_collab",
- "automation_status": "string",
- "state": "string",
- "config": {
- "sample_request_config": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}, - "sample_approval": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}
}, - "schedule": { },
- "creators_to_include": { },
- "creators_to_exclude": { },
- "crm_group_id": "string",
- "ai_enabled": false,
- "end_date": "string",
- "created_at": "string",
- "updated_at": "string",
- "created_via": "string",
- "side_effects": { }
}, - "dry_run": false,
- "side_effects": { }
}Transition automation to stopped. Idempotent.
| automation_id required | integer (Automation Id) |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
{- "data": {
- "automation_id": 0,
- "shop_id": 0,
- "automation_name": "string",
- "automation_type": "target_collab",
- "automation_status": "string",
- "state": "string",
- "config": {
- "sample_request_config": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}, - "sample_approval": {
- "minimum_performance_criteria": {
- "min_shop_gmv": 0
}
}
}, - "schedule": { },
- "creators_to_include": { },
- "creators_to_exclude": { },
- "crm_group_id": "string",
- "ai_enabled": false,
- "end_date": "string",
- "created_at": "string",
- "updated_at": "string",
- "created_via": "string",
- "side_effects": { }
}, - "dry_run": false,
- "side_effects": { }
}Connected inboxes available to back email automations. Read-only; access tokens are never exposed.
The email accounts (inboxes) connected to your shop for email automations. Use an email_account_id from this list when creating an email automation. Archived accounts are omitted; accounts in an error state are still listed, so check status before use ('active' means the inbox is usable). Access tokens are never included.
{- "data": [
- {
- "email_account_id": 0,
- "shop_id": 0,
- "email": "string",
- "status": "string"
}
], - "shops_queried": [
- "string"
]
}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.
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).
Content 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).
API 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.
required | object (PersonalizationPreviewCreator) The single creator to generate a preview for. |
| message_body required | string (Message Body) [ 1 .. 4000 ] characters The DM template with [Block] tokens (mirrors the automation's CREATOR_MESSAGE). |
| tone | string (Tone) Default: "engaging" Enum: "engaging" "bold" "inspirational" |
| fallback_message required | string (Fallback Message) [ 1 .. 4000 ] characters 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 | string (Brand Name) <= 200 characters Default: "" Brand display name used in the copy. Defaults to the shop name when omitted. |
| product_description | string (Product Description) <= 4000 characters Default: "" Pre-resolved [Product Description]. Get it from POST /personalization/product-blocks. |
| product_selling_points | string (Product Selling Points) <= 2000 characters Default: "" Pre-resolved [Product Selling Points]. Get it from POST /personalization/product-blocks. |
| offer_discussion | string (Offer Discussion) <= 2000 characters Default: "" [Offer Discussion] block value — the brand's offer text. |
Word Counts (object) or Word Counts (null) (Word Counts) Optional approximate word-count target per AI block, e.g. {"content_discussion": 25, "product_discussion": 20}. Only content_discussion and product_discussion are honored; clamped server-side. Null uses the brevity default. | |
| name_source | string (Name Source) Default: "first_name" Enum: "first_name" "display_name" Which name fills the creator-name token (CORE-7779). Pass the same value the automation's personalization config carries so the preview matches the send. |
{- "creator": {
- "creator_id": "string",
- "creator_handle": "string",
- "creator_first_name": ""
}, - "message_body": "string",
- "tone": "engaging",
- "fallback_message": "string",
- "brand_name": "",
- "product_description": "",
- "product_selling_points": "",
- "offer_discussion": "",
- "word_counts": {
- "property1": 0,
- "property2": 0
}, - "name_source": "first_name"
}{- "creator_id": "string",
- "message": "string",
- "is_fallback": true,
- "blocks_used": [
- "string"
]
}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.
Both 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.
| product_id required | string (Product Id) [ 1 .. 100 ] characters A product id in this shop's catalog. Blocks resolve empty if it isn't. |
{- "product_id": "string"
}{- "product_description": "",
- "product_selling_points": ""
}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.
| message_body required | string (Message Body) [ 1 .. 4000 ] characters The message body to rewrite. Every [Block] token is preserved. |
| tone | string (Tone) Default: "engaging" Enum: "engaging" "bold" "inspirational" |
{- "message_body": "string",
- "tone": "engaging"
}{- "message": "string"
}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.
| automation_id required | integer (Automation Id) |
{- "automation_id": 0,
- "messages_sent": 0,
- "fallback_used": 0,
- "fallback_rate": 0
}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.
| creator_id required | string (Creator Id) |
| limit | integer (Limit) [ 1 .. 100 ] Default: 20 |
{- "creator_id": "string",
- "count": 0,
- "messages": [
- {
- "automation_id": 0,
- "message": "string",
- "is_fallback": true,
- "tone": "engaging",
- "sent_at": "string"
}
]
}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.
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.
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.
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).
Required. read_write scope, Idempotency-Key header. Pass X-Dry-Run: true to validate without persisting or queueing.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| creator_handle required | string (Creator Handle) [ 1 .. 64 ] characters TikTok @-handle (without the |
| product_id required | string (Product Id) [ 1 .. 64 ] characters TikTok Shop product ID to attach to the invitation. |
| commission_rate required | number (Commission Rate) [ 0 .. 1 ] Affiliate commission rate (0.0-1.0, e.g. 0.20 for 20%). |
Shop Ads Commission Rate (number) or Shop Ads Commission Rate (null) (Shop Ads Commission Rate) 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 required | string (Message) [ 1 .. 500 ] characters TC card message body shown to the creator (max 500 chars). Mirrors |
| invitation_name required | string (Invitation Name) [ 1 .. 30 ] characters Name shown on the TC invitation card (max 30 chars). Same field as |
Valid Until (string) or Valid Until (null) (Valid Until) Date the TC card stops being valid. Defaults to today + 30 days when omitted. After this date the dispatcher stops the underlying automation (see | |
TargetCollabDirectSupportContact (object) or null Brand-side support contact shown on the TC card. Optional ONLY when this shop has a default configured via |
{- "creator_handle": "string",
- "product_id": "string",
- "commission_rate": 1,
- "shop_ads_commission_rate": 1,
- "message": "string",
- "invitation_name": "string",
- "valid_until": "2019-08-24",
- "support_contact": {
- "email": "string",
- "phone": "string"
}
}{- "automation_id": 0,
- "status": "string",
- "creator_id": "string",
- "creator_handle": "string",
- "shop_id": 0,
- "check_status_at": "string",
- "queued_at": "string",
- "dry_run": false
}Read the shop-level default support_contact used as a fallback when a TC create request omits support_contact (CORE-5428).
Returns 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).
{- "shop_id": 0,
- "configured": true,
- "email": "string",
- "phone": "string",
- "updated_at": "string"
}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.
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.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) |
| email required | string (Email) [ 3 .. 255 ] characters Default contact email shown to the creator on the TC card. |
Phone (string) or Phone (null) (Phone) Optional default contact phone. E.164 format recommended. |
{- "email": "string",
- "phone": "string"
}{- "shop_id": 0,
- "configured": true,
- "email": "string",
- "phone": "string",
- "updated_at": "string"
}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), live_gmv (creator LIVE-attributed, from live_sessions — see POST /lives/* for the per-shop currency-aware view), sc_affiliate_live_gmv (creator LIVE GMV as Seller Center itself reports it, under By order source → Creator → Lives — order-date attributed, so it does not match live_gmv; use it to reconcile against a Seller Center export), 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).
| metrics required | Array of strings (Metrics) [ 1 .. 30 ] items Items Enum: "gmv" "total_gmv" "live_gmv" "sc_affiliate_live_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" |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) | |
Granularity (string) or Granularity (null) (Granularity) |
{- "metrics": [
- "gmv"
], - "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "granularity": "day"
}{- "data": {
- "property1": [
- {
- "date": "string",
- "value": 0
}
], - "property2": [
- {
- "date": "string",
- "value": 0
}
]
}, - "granularity": "string",
- "start_date": "string",
- "end_date": "string",
- "currency": "string",
- "shops_queried": [
- "string"
]
}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, plus sc_affiliate_live_gmv (creator LIVE GMV as Seller Center reports it — order-date attributed, so it does not match live_gmv). 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).
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24"
}{- "gmv": 0,
- "total_gmv": 0,
- "live_gmv": 0,
- "sc_affiliate_live_gmv": 0,
- "active_creators": 0,
- "video_views": 0,
- "videos_posted": 0,
- "samples_approved": 0,
- "gmv_per_video": 0,
- "gmv_per_sample": 0,
- "sample_requests": 0,
- "gmv_driving_videos": 0,
- "new_creators_posting": 0,
- "open_collabs": 0,
- "accepted_tc_count": 0,
- "creators_reached": 0,
- "creators_messaged": 0,
- "tc_invites_sent": 0,
- "emails_sent": 0,
- "dm_responses": 0,
- "reply_rate": 0,
- "orders": 0,
- "units_sold": 0,
- "aov": 0,
- "ctr": 0,
- "conversion_rate": 0,
- "errors": [
- "string"
], - "start_date": "string",
- "end_date": "string",
- "currency": "string",
- "shops_queried": [
- "string"
]
}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.
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).
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.
Single-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.
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). Defaults to 29 days before | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). Defaults to yesterday (today is excluded because the daily row is still incomplete). |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24"
}{- "shop_id": 0,
- "currency": "string",
- "currency_code": "string",
- "granularity": "daily",
- "start_date": "string",
- "end_date": "string",
- "series": [
- {
- "date": "string",
- "gmv": 0,
- "orders": 0,
- "items_sold": 0,
- "customers": 0,
- "aov": 0,
- "channels": {
- "video": {
- "gmv": 0,
- "affiliate": 0,
- "seller": 0
}, - "live": {
- "gmv": 0,
- "affiliate": 0,
- "seller": 0
}, - "product_card": {
- "gmv": 0,
- "shop_tab": 0,
- "search": 0
}
}, - "traffic": {
- "product_impressions": 0,
- "product_clicks": 0
}
}
]
}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.
Same 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.
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). Defaults to 29 days before | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). Defaults to yesterday. |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24"
}{- "shop_id": 0,
- "currency": "string",
- "currency_code": "string",
- "start_date": "string",
- "end_date": "string",
- "day_count": 0,
- "gmv": 0,
- "orders": 0,
- "items_sold": 0,
- "customers": 0,
- "aov": 0,
- "channels": {
- "video": {
- "gmv": 0,
- "affiliate": 0,
- "seller": 0
}, - "live": {
- "gmv": 0,
- "affiliate": 0,
- "seller": 0
}, - "product_card": {
- "gmv": 0,
- "shop_tab": 0,
- "search": 0
}
}, - "traffic": {
- "product_impressions": 0,
- "product_clicks": 0
}
}Per-shop TikTok Shop Performance Score (SPS) and its health sub-metrics (on-time dispatch, negative-review rate, etc.) — daily trend and latest snapshot. Sourced from TikTok's official shop_performances API.
Daily Shop Performance Score for one shop from TikTok's official shop_performances/overview API — the same SPS shown in Seller Center. Each point carries sps_score (0-5), sps_tier, peer_percentile, and a dimensions breakdown of the sub-metrics (on-time dispatch, negative-review rate, etc.).
Single-shop only — set x-shop-id to a specific shop ID. Defaults to the last 30 days ending yesterday. Max range 90 days. Only shops on the official-API sync path are covered; an empty series is a normal 200 response (history builds from the first daily pull).
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). Defaults to 29 days before | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). Defaults to yesterday (today's snapshot is not recorded until the daily pull runs). |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24"
}{- "shop_id": 0,
- "start_date": "string",
- "end_date": "string",
- "series": [
- {
- "date": "string",
- "sps_score": 0,
- "sps_tier": "string",
- "peer_percentile": 0,
- "dimensions": [
- {
- "name": "string",
- "score": 0,
- "status": "string",
- "status_text": "string",
- "weight": 0
}
]
}
]
}The most recent Shop Performance Score snapshot for one shop — the current sps_score (0-5), sps_tier, peer_percentile, and the per-sub-metric dimensions breakdown.
Single-shop only. snapshot is null when the shop has no SPS data yet (non-official-API shop, or the first daily pull hasn't run).
POST /shop-health/latest request body. Returns the most recent SPS snapshot for the shop — takes no parameters.
{ }{- "shop_id": 0,
- "snapshot": {
- "date": "string",
- "sps_score": 0,
- "sps_tier": "string",
- "peer_percentile": 0,
- "dimensions": [
- {
- "name": "string",
- "score": 0,
- "status": "string",
- "status_text": "string",
- "weight": 0
}
]
}
}Every stored snapshot of TikTok's six Shop Performance Score metrics for one shop in the window (NRR, NBFR, SFCR, OTDR, AHT, IM_DSAT): status (EXCELLENT/GOOD/POOR/CRITICAL/NIL), value with unit, thresholds, evaluation window, and how many problem items TikTok reported. latest_snapshot_date names the newest day; a metric absent on that day has no data. Window: a preset or explicit dates, max 365 days. Single-shop only.
Preset (string) or Preset (null) (Preset) Relative window ending at the shop's newest settled day: | |
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). | |
Array of Metric Codes (strings) or Metric Codes (null) (Metric Codes) Restrict to these metric codes; all six when omitted. |
{- "preset": "string",
- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "metric_codes": [
- "NRR"
]
}{- "shop_id": 0,
- "start_date": "string",
- "end_date": "string",
- "latest_snapshot_date": "string",
- "snapshot_count": 0,
- "points": [
- {
- "date": "string",
- "metric_code": "NRR",
- "metric_name": "string",
- "dimension": "string",
- "score": 0,
- "value": 0,
- "value_unit": "string",
- "status": "EXCELLENT",
- "status_text": "string",
- "excellent_threshold": 0,
- "poor_threshold": 0,
- "evaluation_window_start": "string",
- "evaluation_window_end": "string",
- "is_top_reason": false,
- "top_reason_text": "string",
- "problems_upstream_total": 0,
- "problems_stored": 0,
- "problems_complete": true,
- "problems_truncated": true
}
]
}The problem items TikTok attributes to one metric on one snapshot day (default: the latest stored snapshot), paged and ordered by TikTok's page then item key. pagination.stored_total is what Reacher holds; upstream_total/upstream_truncated are TikTok's own count and whether it cut the list. snapshot_date is null with an empty page when nothing is stored. Single-shop only.
| metric_code required | string (Metric Code) Enum: "NRR" "NBFR" "SFCR" "OTDR" "AHT" "IM_DSAT" |
Snapshot Date (string) or Snapshot Date (null) (Snapshot Date) Snapshot day; defaults to the latest stored snapshot for this metric. | |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
Version (integer) or Version (null) (Version) Collector run to page through, as returned in |
{- "metric_code": "NRR",
- "snapshot_date": "2019-08-24",
- "page": 1,
- "page_size": 20,
- "version": 1
}{- "shop_id": 0,
- "metric_code": "NRR",
- "snapshot_date": "string",
- "pagination": {
- "page": 0,
- "page_size": 0,
- "stored_total": 0
}, - "upstream_total": 0,
- "upstream_truncated": true,
- "version": 0,
- "items": [
- {
- "item_key": "string",
- "order_id": "string",
- "return_order_id": "string",
- "chat_record_id": "string",
- "product_id": "string",
- "sku_id": "string",
- "product_name": "string",
- "reason": "string",
- "rating": 0,
- "review_count": 0,
- "event_time": "string",
- "deliver_time": "string",
- "expect_deliver_time": "string",
- "actual_deliver_time": "string",
- "duration_hours": 0,
- "page": 0
}
]
}Merchant profit & loss straight from your TikTok Seller Center finance export: window totals as an ordered waterfall, the running P&L bucketed by day / week / bi-weekly / month, and order x SKU detail with its fee breakdown. Every figure is TikTok's own, summed and never recomputed, so any cadence reconciles to Seller Center exactly. Rows are bucketed by order PAID date in the shop's Seller Center timezone, so recent windows keep moving as orders settle — read data_status on every response for freshness. Costs TikTok cannot see are supplied separately: store cost of goods per SKU and per-unit sample cost with PUT /pnl/costs, then read POST /pnl/profit for the contribution profit they imply. POST /pnl/skus rolls the same figures up per SKU as a gross / contribution P&L (revenue and per-SKU fees less cost of goods); per-SKU ad spend is not available, so the true net bottom line stays on the summary and profit surfaces. Single-shop only, and enabled per shop on request.
The per-product side of the P&L that POST /pnl/summary reports in aggregate: every active product in the shop with its SKU pricing (sale and original price in currency units, not cents), stock, the derived discount_pct, and the affiliate commission_rate / shop_ads_commission_rate from the shop's most recent Target Collaboration config.
gmv_max is shop-level, not per product: whether GMV Max is currently running and the average ROAS actually achieved across its active campaigns. tiktok_fee_pct is the platform's flat fee assumption used by the calculator.
These are the inputs to a margin calculation, not settled money — for what TikTok actually paid out, use POST /pnl/summary or POST /pnl/orders. Single-shop only. A shop with no catalogued products returns an empty products list as a normal 200.
{- "products": [
- {
- "product_id": "string",
- "title": "string",
- "primary_image_url": "string",
- "category_chain": [ ],
- "brand_name": "string",
- "currency": "USD",
- "skus": [ ],
- "commission": {
- "commission_rate": 0,
- "shop_ads_commission_rate": 0
}, - "discount_pct": 0
}
], - "shop_id": 0,
- "tiktok_fee_pct": 6,
- "gmv_max": {
- "active": false,
- "avg_roas": 0,
- "tooltip": "Based on average ROAS across all active campaigns in your shop."
}
}Window totals as an ordered waterfall, summed from TikTok's own daily P&L series. Rows are bucketed by order PAID date in the shop's Seller Center timezone. Recent days keep moving as orders settle — check data_status for freshness.
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). Defaults to 29 days before | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). Defaults to yesterday — the current day is still accumulating. |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24"
}{- "shop_id": 0,
- "seller_id": "string",
- "currency": "string",
- "start_date": "string",
- "end_date": "string",
- "days_with_data": 0,
- "totals": {
- "gross_sales": 0,
- "refunds": 0,
- "referral_fee": 0,
- "affiliate": 0,
- "gmv_max": 0,
- "campaigns": 0,
- "managed_services": 0,
- "shipping_and_warehousing_cost": 0,
- "others": 0,
- "net_earnings": 0
}, - "rollups": {
- "property1": 0,
- "property2": 0
}, - "data_status": {
- "state": "never_run",
- "last_synced_at": "string",
- "last_window": {
- "start_date": "string",
- "end_date": "string"
}
}
}The running P&L: the same waterfall bucketed by day, week, bi-weekly or month. Buckets are summed from TikTok's daily rows, so any cadence reconciles to Seller Center exactly.
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). | |
| cadence | string (Cadence) Default: "day" Enum: "day" "week" "biweekly" "month" Bucket size. Buckets are summed from TikTok's own daily rows, so they reconcile to Seller Center exactly. Week and bi-weekly anchor on Monday. |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "cadence": "day"
}{- "shop_id": 0,
- "seller_id": "string",
- "currency": "string",
- "cadence": "string",
- "series": [
- {
- "gross_sales": 0,
- "refunds": 0,
- "referral_fee": 0,
- "affiliate": 0,
- "gmv_max": 0,
- "campaigns": 0,
- "managed_services": 0,
- "shipping_and_warehousing_cost": 0,
- "others": 0,
- "net_earnings": 0,
- "period_start": "string"
}
], - "data_status": {
- "state": "never_run",
- "last_synced_at": "string",
- "last_window": {
- "start_date": "string",
- "end_date": "string"
}
}
}One row per order x SKU with its fee breakdown, filterable by order source, settled state, product or SKU — the same filters applied by hand to the Seller Center export. Unsettled lines are included by default and their amounts still move.
Start Date (string) or Start Date (null) (Start Date) Inclusive paid-date window start. | |
End Date (string) or End Date (null) (End Date) Inclusive paid-date window end. | |
Order Source (string) or Order Source (null) (Order Source) Filter by TikTok's own order-source value. | |
Settled (boolean) or Settled (null) (Settled) True = only settled orders, False = only unsettled. Unsettled amounts still move as TikTok settles them. | |
Product Id (string) or Product Id (null) (Product Id) Filter to one product. | |
Sku Id (string) or Sku Id (null) (Sku Id) Filter to one SKU. | |
| limit | integer (Limit) [ 1 .. 1000 ] Default: 100 Page size. |
| offset | integer (Offset) >= 0 Default: 0 Page offset. |
Amounts Fields (string) or Array of Amounts Fields (strings) or Amounts Fields (null) (Amounts Fields) Which fee columns each row carries. |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "order_source": "Video",
- "settled": true,
- "product_id": "string",
- "sku_id": "string",
- "limit": 100,
- "offset": 0,
- "amounts_fields": "standard"
}{- "shop_id": 0,
- "seller_id": "string",
- "total": 0,
- "limit": 0,
- "offset": 0,
- "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0
}, - "selected_fields": [
- "string"
], - "orders": [
- {
- "order_id": "string",
- "sku_id": "string",
- "product_id": "string",
- "product_name": "string",
- "sku_name": "string",
- "quantity": 0,
- "order_paid_date": "string",
- "order_settled_date": "string",
- "order_status": "string",
- "order_source": [
- "string"
], - "is_settled": true,
- "unsettled_reason": "string",
- "estimated_settle_time": "string",
- "sample_order_type": "string",
- "linked_statement_id": "string",
- "currency": "string",
- "amounts": { }
}
], - "data_status": {
- "state": "never_run",
- "last_synced_at": "string",
- "last_window": {
- "start_date": "string",
- "end_date": "string"
}
}
}One row per SKU: units sold and the fee lines from the order-level export summed over the window, joined to your cost of goods, with a gross profit and margin.
This is a GROSS / CONTRIBUTION view, not a net bottom line. The fees here are the ones TikTok books against the order itself — referral, affiliate commission, refunds, discounts and the like — and ad spend is NOT among them: TikTok does not attribute ad spend to a single SKU, so gross_profit is revenue less those per-SKU fees less COGS, and stops one step short of net earnings. For the true shop-level bottom line that includes ad spend, use POST /pnl/summary and POST /pnl/profit. If you request the net_earnings or total_expense columns they are the order-line totals and likewise exclude ad spend.
gross_profit is an upper bound while units_uncosted is non-zero — uncosted units are counted, never assumed free. Windowed by order PAID date; single shop only.
Start Date (string) or Start Date (null) (Start Date) Inclusive paid-date window start (YYYY-MM-DD). | |
End Date (string) or End Date (null) (End Date) Inclusive paid-date window end (YYYY-MM-DD). | |
Product Id (string) or Product Id (null) (Product Id) Filter to one product. | |
Sku Id (string) or Sku Id (null) (Sku Id) Filter to one SKU. | |
| limit | integer (Limit) [ 1 .. 1000 ] Default: 100 Page size (SKUs per page). |
| offset | integer (Offset) >= 0 Default: 0 Page offset. |
Amounts Fields (string) or Array of Amounts Fields (strings) or Amounts Fields (null) (Amounts Fields) Which fee columns each SKU's | |
| sort | string (Sort) Default: "gross_sales" Enum: "gross_sales" "gross_profit" "units" Sort key, always descending, tie-broken by product_id then sku_id so paging is stable. |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "product_id": "string",
- "sku_id": "string",
- "limit": 100,
- "offset": 0,
- "amounts_fields": "standard",
- "sort": "gross_sales"
}{- "shop_id": 0,
- "seller_id": "string",
- "currency": "string",
- "start_date": "string",
- "end_date": "string",
- "total": 0,
- "limit": 0,
- "offset": 0,
- "selected_fields": [
- "string"
], - "currency_mismatched_costs": 0,
- "skus": [
- {
- "product_id": "string",
- "sku_id": "string",
- "sku_name": "string",
- "product_name": "string",
- "units": 0,
- "amounts": {
- "property1": 0,
- "property2": 0
}, - "cogs": 0,
- "gross_profit": 0,
- "gross_margin_pct": 0,
- "units_costed": 0,
- "units_uncosted": 0,
- "currency": "string"
}
], - "data_status": {
- "state": "never_run",
- "last_synced_at": "string",
- "last_window": {
- "start_date": "string",
- "end_date": "string"
}
}
}Every cost input stored for this shop: cost of goods per SKU and per-unit sample cost per product. Returns full history — a row per effective_from — not just what is in force today, so a caller restating an old window can see which amount applied then. sku_id is null on the product-level default.
Cost Type (string) or Cost Type (null) (Cost Type) Filter to one type: cogs | sample_unit_cost. | |
Product Id (string) or Product Id (null) (Product Id) Filter to one product. | |
Sku Id (string) or Sku Id (null) (Sku Id) Filter to one SKU. Pass an empty string to see only product-level defaults. |
{- "shop_id": 0,
- "costs": [
- {
- "cost_id": 0,
- "id": 0,
- "product_id": "string",
- "sku_id": "string",
- "cost_type": "string",
- "amount": 0,
- "currency": "string",
- "effective_from": "string",
- "notes": "string",
- "source": "string",
- "updated_at": "string"
}
]
}Insert or update cost inputs in bulk — the endpoint a spreadsheet or CSV import writes through. Rows are keyed on (product_id, sku_id, cost_type, effective_from): re-sending a key overwrites its amount rather than stacking a second cost onto the same units, so replaying the same import is safe. Omit sku_id to set a product-level default that applies to every SKU without one of its own. Requires read_write scope and a single shop.
required | Array of objects (Costs) [ 1 .. 500 ] items Rows to insert or update, keyed on (product_id, sku_id, cost_type, effective_from). Re-sending a key overwrites its amount rather than stacking a second cost onto the same units. If the same key appears twice in one request, the last one wins. |
{- "costs": [
- {
- "product_id": "string",
- "sku_id": "string",
- "cost_type": "cogs",
- "amount": 0,
- "currency": "USD",
- "effective_from": "2019-08-24",
- "notes": "string"
}
]
}{- "shop_id": 0,
- "costs": [
- {
- "cost_id": 0,
- "id": 0,
- "product_id": "string",
- "sku_id": "string",
- "cost_type": "string",
- "amount": 0,
- "currency": "string",
- "effective_from": "string",
- "notes": "string",
- "source": "string",
- "updated_at": "string"
}
]
}Remove one stored cost by its id. The units it covered become uncosted again and drop out of contribution_profit — they are not treated as free-of-charge silently, they are reported as uncosted. Scoped to this shop, so an id belonging to another shop returns 404. Requires read_write scope and a single shop.
| cost_id required | integer (Cost Id) |
{- "shop_id": 0,
- "costs": [
- {
- "cost_id": 0,
- "id": 0,
- "product_id": "string",
- "sku_id": "string",
- "cost_type": "string",
- "amount": 0,
- "currency": "string",
- "effective_from": "string",
- "notes": "string",
- "source": "string",
- "updated_at": "string"
}
]
}TikTok's bottom line carried down by the costs it cannot see. net_earnings is taken from the daily series unchanged — the only source that includes GMV Max ad spend — and cost of goods and sample cost are subtracted from it. Set cadence to get the running series as well as the totals.
Units with no cost on file are COUNTED, never assumed to be zero, so contribution_profit is an upper bound while units_uncosted or sample_units_uncosted is non-zero. Use POST /pnl/costs/coverage to get the list of what is missing.
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). | |
| cadence | string (Cadence) Default: "total" Enum: "total" "day" "week" "biweekly" "month"
|
| sample_basis | string (Sample Basis) Default: "shipped" Enum: "shipped" "approved" "requested" Which sample milestone the cost is charged against. |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "cadence": "total",
- "sample_basis": "shipped"
}{- "shop_id": 0,
- "seller_id": "string",
- "currency": "string",
- "start_date": "string",
- "end_date": "string",
- "cadence": "string",
- "sample_basis": "string",
- "days_with_data": 0,
- "totals": {
- "net_earnings": 0,
- "cogs": 0,
- "sample_cost": 0,
- "contribution_profit": 0,
- "units": 0,
- "units_costed": 0,
- "units_uncosted": 0,
- "units_on_refunded_lines": 0,
- "sample_units": 0,
- "sample_units_costed": 0,
- "sample_units_uncosted": 0
}, - "series": [
- {
- "net_earnings": 0,
- "cogs": 0,
- "sample_cost": 0,
- "contribution_profit": 0,
- "units": 0,
- "units_costed": 0,
- "units_uncosted": 0,
- "units_on_refunded_lines": 0,
- "sample_units": 0,
- "sample_units_costed": 0,
- "sample_units_uncosted": 0,
- "period_start": "string"
}
], - "currency_mismatched_costs": 0,
- "data_status": {
- "state": "never_run",
- "last_synced_at": "string",
- "last_window": {
- "start_date": "string",
- "end_date": "string"
}
}
}The SKUs that sold and the products that shipped samples in this window with no cost on file, ordered by how many units each one represents — the worklist for making POST /pnl/profit complete, biggest impact first. coverage_pct is by units, not by SKU count, so one missing best-seller outranks fifty missing long-tail SKUs.
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). | |
| sample_basis | string (Sample Basis) Default: "shipped" Enum: "shipped" "approved" "requested" |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "sample_basis": "shipped"
}{- "shop_id": 0,
- "seller_id": "string",
- "currency": "string",
- "start_date": "string",
- "end_date": "string",
- "sample_basis": "string",
- "cogs": {
- "units": 0,
- "units_costed": 0,
- "coverage_pct": 0,
- "missing_count": 0,
- "missing": [
- {
- "product_id": "string",
- "sku_id": "string",
- "product_name": "string",
- "sku_name": "string",
- "units": 0
}
], - "truncated": true
}, - "samples": {
- "sample_units": 0,
- "sample_units_costed": 0,
- "coverage_pct": 0,
- "missing_count": 0,
- "missing": [
- {
- "product_id": "string",
- "sample_units": 0
}
], - "truncated": true
}
}Credits, clawbacks and manual corrections TikTok posts against a statement rather than an order. They never appear on order rows, so a P&L built only from /pnl/orders silently omits them — net_amount is the figure to carry across.
Windowed on the date TikTok raised the adjustment. settled_date is null until it reaches a payout.
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). | |
| limit | integer (Limit) [ 1 .. 1000 ] Default: 100 Page size. |
| offset | integer (Offset) >= 0 Default: 0 Page offset. |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "limit": 100,
- "offset": 0
}{- "shop_id": 0,
- "seller_id": "string",
- "total": 0,
- "limit": 0,
- "offset": 0,
- "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0
}, - "net_amount": 0,
- "adjustments": [
- {
- "adjustment_id": "string",
- "adjustment_type": "string",
- "reason": "string",
- "amount": 0,
- "created_date": "string",
- "settled_date": "string",
- "linked_statement_id": "string",
- "linked_payout_id": "string",
- "currency": "string"
}
], - "data_status": {
- "state": "never_run",
- "last_synced_at": "string",
- "last_window": {
- "start_date": "string",
- "end_date": "string"
}
}
}What the CUSTOMER paid for each order x SKU, as opposed to what the seller earned: subtotal before and after discount, which party funded each discount, shipping, retail delivery fee and tax.
Paired to order lines on (order, SKU) to get a paid date, since this sheet carries none of its own. TikTok does not guarantee that pairing is 1:1, so a payment row with no matching order line falls outside the window rather than being invented into it.
Start Date (string) or Start Date (null) (Start Date) Inclusive paid-date window start. | |
End Date (string) or End Date (null) (End Date) Inclusive paid-date window end. | |
Product Id (string) or Product Id (null) (Product Id) Filter to one product. | |
Sku Id (string) or Sku Id (null) (Sku Id) Filter to one SKU. | |
| limit | integer (Limit) [ 1 .. 1000 ] Default: 100 Page size. |
| offset | integer (Offset) >= 0 Default: 0 Page offset. |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "product_id": "string",
- "sku_id": "string",
- "limit": 100,
- "offset": 0
}{- "shop_id": 0,
- "seller_id": "string",
- "total": 0,
- "limit": 0,
- "offset": 0,
- "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0
}, - "payments": [
- {
- "order_id": "string",
- "sku_id": "string",
- "currency": "string",
- "amounts": {
- "property1": 0,
- "property2": 0
}
}
], - "data_status": {
- "state": "never_run",
- "last_synced_at": "string",
- "last_window": {
- "start_date": "string",
- "end_date": "string"
}
}
}Affiliate Center -> Orders, over the API: one row per order x SKU with the creator, the content (video / LIVE / showcase) that drove the sale, the commission model and rates, and estimated vs actual commission side by side. This is the order-level audit trail behind /creators/performance and /videos/performance. Rows are keyed on TikTok's seller and bucketed by order CREATED date in the shop's Affiliate Center timezone; status and actual amounts keep moving for weeks as commission settles, so read data_status for freshness. Single-shop only. A shop whose export collection is not yet enabled reads data_status.state == "never_run".
One row per order x SKU from Affiliate Center -> Orders: which creator drove it, through which content (video / LIVE / showcase), under which commission model and rates, and the estimated vs actual commission side by side. Filter by an order-created date window, creator handle, content ID or type, product, SKU, order status or settled state — the same filters as the Affiliate Center UI. Estimated amounts are set when the order is created; actual amounts and commission_paid_at fill in as TikTok settles, typically weeks later, so recent windows keep moving — read data_status for freshness. Defaults to the last 30 complete days.
Start Date (string) or Start Date (null) (Start Date) Inclusive window start on the order CREATED date (YYYY-MM-DD), in the shop's Affiliate Center timezone. Defaults to 30 days before end_date. | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). Defaults to yesterday — the current day is still accumulating. | |
Creator Handle (string) or Creator Handle (null) (Creator Handle) Filter to one creator by TikTok username. A leading '@' is ignored; case-insensitive. | |
Content Id (string) or Content Id (null) (Content Id) Filter to the video, LIVE or showcase that drove the sale (TikTok content ID). | |
Content Type (string) or Content Type (null) (Content Type) Filter by content type, case-insensitive. Values TikTok ships: Video, Livestream, Showcase, Affiliate Product Page. | |
Product Id (string) or Product Id (null) (Product Id) Filter to one product. | |
Sku Id (string) or Sku Id (null) (Sku Id) Filter to one SKU. | |
Order Status (string) or Order Status (null) (Order Status) Filter by TikTok's own commission status, case-insensitive. Values seen: Pending, Settled, Ineligible, Awaiting payment. | |
Settled (boolean) or Settled (null) (Settled) True = only lines whose commission has settled, False = everything else (pending, ineligible, awaiting payment). Unsettled amounts still move. | |
| limit | integer (Limit) [ 1 .. 1000 ] Default: 100 Page size. |
| offset | integer (Offset) [ 0 .. 100000 ] Default: 0 Page offset. Capped at 100,000: deeper pages still scan and sort everything before them, so narrow the date window instead. |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "creator_handle": "string",
- "content_id": "string",
- "content_type": "string",
- "product_id": "string",
- "sku_id": "string",
- "order_status": "string",
- "settled": true,
- "limit": 100,
- "offset": 0
}{- "shop_id": 0,
- "seller_id": "string",
- "start_date": "string",
- "end_date": "string",
- "total": 0,
- "limit": 0,
- "offset": 0,
- "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0
}, - "data": [
- {
- "order_id": "string",
- "sku_id": "string",
- "product_id": "string",
- "product_name": "string",
- "quantity": 0,
- "price": 0,
- "payment_amount": 0,
- "currency": "string",
- "fully_refunded": true,
- "payment_method": "string",
- "order_status": "string",
- "is_settled": true,
- "creator_handle": "string",
- "content_type": "string",
- "content_id": "string",
- "commission_model": "string",
- "standard_commission_rate": 0,
- "shop_ads_commission_rate": 0,
- "estimated": {
- "commission_base": 0,
- "standard_commission": 0,
- "shop_ads_commission": 0,
- "cofunded_creator_bonus": 0
}, - "actual": {
- "commission_base": 0,
- "standard_commission": 0,
- "shop_ads_commission": 0,
- "cofunded_creator_bonus": 0
}, - "order_created_at": "string",
- "paid_at": "string",
- "delivered_at": "string",
- "commission_paid_at": "string",
- "platform": "string"
}
], - "transactions": [
- {
- "order_id": "string",
- "sku_id": "string",
- "product_id": "string",
- "product_name": "string",
- "quantity": 0,
- "price": 0,
- "payment_amount": 0,
- "currency": "string",
- "fully_refunded": true,
- "payment_method": "string",
- "order_status": "string",
- "is_settled": true,
- "creator_handle": "string",
- "content_type": "string",
- "content_id": "string",
- "commission_model": "string",
- "standard_commission_rate": 0,
- "shop_ads_commission_rate": 0,
- "estimated": {
- "commission_base": 0,
- "standard_commission": 0,
- "shop_ads_commission": 0,
- "cofunded_creator_bonus": 0
}, - "actual": {
- "commission_base": 0,
- "standard_commission": 0,
- "shop_ads_commission": 0,
- "cofunded_creator_bonus": 0
}, - "order_created_at": "string",
- "paid_at": "string",
- "delivered_at": "string",
- "commission_paid_at": "string",
- "platform": "string"
}
], - "data_status": {
- "state": "never_run",
- "last_synced_at": "string",
- "last_window": {
- "start_date": "string",
- "end_date": "string"
}
}
}Creator LIVE-attributed GMV — window totals and per-session rows from TikTok's Affiliate Center LIVE data. Distinct from the gmv field (all affiliate revenue); the same number is on POST /metrics/* as live_gmv. Single-shop, currency-aware.
The five LIVE performance cards the portal shows above the session table — Live GMV, Live Units Sold, Live Count, Avg Engagement Rate and Avg Viewing Duration. Each card carries its value, unit, the percent change against the immediately preceding window of equal length, and chart_data: one point per day of the window, zero-filled on days with no sessions so the series length always matches the range.
Differs from POST /lives/summary — that returns flat window totals. This returns the same totals plus period-over-period change and a daily series, and accepts the portal's filters / search so the cards can be scoped to a creator or a GMV band. Engagement rate is a decimal (0.0-1.0), durations are seconds.
Single-shop only; defaults to the last 30 days ending today. LIVE analytics is region-gated — shops outside the supported regions get a 403 REGION_NOT_SUPPORTED. A shop with no sessions returns zeroed cards as a normal 200.
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) | |
LivesFilters (object) or null | |
Search (string) or Search (null) (Search) |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "filters": {
- "gmv": {
- "min": 0,
- "max": 0
}, - "views": {
- "min": 0,
- "max": 0
}, - "duration": {
- "min": 0,
- "max": 0
}, - "unitsSold": {
- "min": 0,
- "max": 0
}, - "likes": {
- "min": 0,
- "max": 0
}, - "comments": {
- "min": 0,
- "max": 0
}, - "ctr": {
- "min": 0,
- "max": 0
}, - "engagementRate": {
- "min": 0,
- "max": 0
}
}, - "search": "string"
}{- "success": true,
- "metrics": [
- {
- "label": "string",
- "value": 0,
- "unit": "string",
- "change": 0,
- "comparison_text": "string",
- "tooltip_text": "string",
- "chart_data": [
- 0
]
}
]
}The per-product breakdown behind one or more LIVE sessions: for each product featured in the session, its LIVE GMV, units sold, orders, average order value, creator commission and refunds. This is the session x product grain — POST /lives/list gives the session totals, this explains what inside the session earned them.
Pass the key values from POST /lives/list rows in live_session_ids (up to 50 per request). Results are keyed back by session ID in request order. Sessions with no product rows — including any ID that belongs to a different shop, or a shop still on the legacy LIVE data path — return an empty products list rather than an error. Single-shop only.
| live_session_ids required | Array of strings (Live Session Ids) [ 1 .. 50 ] items LIVE session IDs to break down — the |
{- "live_session_ids": [
- "string"
]
}{- "shop_id": 0,
- "sessions": [
- {
- "live_session_id": "string",
- "products": [
- {
- "product_id": "string",
- "product_name": "string",
- "cover": "string",
- "categories": null,
- "live_gmv": 0,
- "items_sold": 0,
- "orders": 0,
- "avg_order_value": 0,
- "commission": 0,
- "refunded_gmv": 0,
- "refunded_items": 0
}
]
}
]
}Window totals for creator LIVE-attributed GMV for one shop, from customers.live_sessions (the Affiliate Center LIVE value shops see on /insights/transaction-analysis → LIVE streams).
Differs from POST /metrics/summary field gmv — that is ALL affiliate revenue (video + live + product card). This isolates the LIVE-stream slice only. The live_gmv field on /metrics/summary is the same number as a simple cross-shop scalar; this endpoint adds per-shop currency, session count, engagement and viewing duration.
Single-shop only — set x-shop-id to a specific shop ID. Defaults to the last 30 days ending today. Empty totals (live_count 0) are a normal 200 response — live-session coverage is data-presence driven.
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). Defaults to 29 days before | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). Defaults to today (live sessions can land same-day, unlike the Seller Center daily rollup). |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24"
}{- "shop_id": 0,
- "currency": "string",
- "currency_code": "string",
- "start_date": "string",
- "end_date": "string",
- "live_count": 0,
- "live_gmv": 0,
- "items_sold": 0,
- "avg_engagement_rate": 0,
- "avg_viewing_duration": 0
}Paginated per-session view of creator LIVE-attributed GMV for one shop — one row per affiliate LIVE session with creator, timestamps, GMV, units, and engagement.
Single-shop only. Defaults to the last 30 days ending today. Sort by live_gmv (default), start_time, units_sold or views. Empty sessions is a normal 200 response for shops without live-session data.
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). Defaults to 29 days before | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). Defaults to today. | |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
| sort_by | string (Sort By) Default: "live_gmv" Enum: "live_gmv" "start_time" "units_sold" "views" |
| sort_dir | string (Sort Dir) Default: "desc" Enum: "asc" "desc" |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "page": 1,
- "page_size": 20,
- "sort_by": "live_gmv",
- "sort_dir": "asc"
}{- "shop_id": 0,
- "currency": "string",
- "currency_code": "string",
- "start_date": "string",
- "end_date": "string",
- "page": 0,
- "page_size": 0,
- "total": 0,
- "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0
}, - "sessions": [
- {
- "live_session_id": "string",
- "title": "string",
- "creator_handle": "string",
- "start_time": "string",
- "end_time": "string",
- "duration_seconds": 0,
- "live_gmv": 0,
- "units_sold": 0,
- "views": 0,
- "likes": 0,
- "comments": 0,
- "ctr": 0,
- "engagement_rate": 0,
- "new_followers": 0,
- "avg_viewing_duration": 0
}
]
}Everything behind one stage of the affiliate funnel returned by GET /funnel: creator_count and percentage_of_funnel, the insights block (unique creators, total entries, conversion rate from the previous stage with its conversion_label, and median days spent in the status), the CRM group_id / group_filters that define the stage, the automation_templates available for it, and the active_automations currently working it.
stage_id accepts the stage keys from the funnel overview — e.g. sample-requested, sample-approved, content-posted, content-unfulfilled, gmv-generated, top-creators.
Set include_products=true to also get the per-product breakdown of the stage (one row per product with stage-relevant metrics such as creators_in_stage, gmv_generated, avg_views, avg_gmv, spark_code_collected); metrics that don't apply to the stage come back as null. Products are paginated independently via products_page / products_page_size and can be narrowed with product_ids.
Single-shop only. Data covers the last 90 days, matching the dashboard.
| stage_id required | string (Stage Id) <= 100 characters Funnel stage key, e.g. 'sample-requested' or 'gmv-generated'. |
| include_products | boolean (Include Products) Default: false Also return the per-product breakdown for this stage. |
Product Ids (string) or Product Ids (null) (Product Ids) Comma-separated product IDs to narrow the per-product breakdown; ignored unless include_products=true. | |
| products_page | integer (Products Page) >= 1 Default: 1 Per-product breakdown page. |
| products_page_size | integer (Products Page Size) [ 1 .. 200 ] Default: 20 Per-product breakdown rows per page. |
{- "shop_id": 0,
- "stage": {
- "stage_id": "string",
- "title": "string",
- "description": "string",
- "group_id": "string",
- "creator_count": 0,
- "percentage_of_funnel": 0,
- "insights": {
- "total_creators": 0,
- "total_in_stage": 0,
- "advanced_to_next_percentage": 0,
- "conversion_label": "of previous status",
- "median_time_in_status_days": 0
}, - "automation_templates": [
- {
- "id": "string",
- "title": "string",
- "description": "string"
}
], - "active_automations": [
- {
- "automation_id": 0,
- "automation_name": "string",
- "automation_type": "string",
- "sample_requests": 0,
- "last_finished_at": "2019-08-24T14:15:22Z",
- "status": "string",
- "status_msg": "string",
- "status_details": "string",
- "creators_remaining": 0,
- "creators_reached": 0,
- "skipped": 0,
- "total_creators": 0,
- "crm_group_id": "string",
- "crm_group_name": "string",
- "ai_enabled": false,
- "created_at": "2019-08-24T14:15:22Z",
- "target_collab_cleanup_creators_reached": 0
}
], - "group_filters": { }
}, - "products": {
- "stage_id": "string",
- "products": [
- {
- "product_id": "string",
- "product_name": "string",
- "image_url": "string",
- "metrics": {
- "property1": 0,
- "property2": 0
}
}
], - "total": 0,
- "page": 0,
- "page_size": 0
}
}The paginated sample-request pipeline for one shop — the rows behind the Sample Requests board. Each row carries the creator (handle, followers, level, rising-star / fast-growing signals, CRM tags), the product, the request's status and timestamps, and the creator's performance metrics (post rate, PPS score, average views, engagement rate, GMV per sample, videos per sample).
Filter by pipeline tab (status), product, SKU, creator profile, sourcing automation, tag, date range, free-text search, and any of the metric ranges (min_* / max_*). product_ids and sku_ids are AND-ed: passing both narrows to those SKUs rather than widening. show_archived returns the archived board instead; show_expiring narrows to requests about to expire. Response also reports archivedCount (requests hidden because the creator is archived), expiringCount, lastUpdatedAt (when this shop was last observed on TikTok; null if never) and a dataFreshness stamp.
Single-shop only, and only for shops on the CRM v2 pipeline — others get a 404 and should use /samples.
| page | integer (Page) >= 1 Default: 1 Page number, 1-based. |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 Rows per page (max 100). |
Status (string) or Status (null) (Status) Pipeline tab to filter to. One of: toReview, readyToShip, shipped, pendingContent, completed, cancelled. | |
Product Ids (string) or Product Ids (null) (Product Ids) Comma-separated product IDs. | |
Sku Ids (string) or Sku Ids (null) (Sku Ids) Comma-separated SKU IDs. AND-ed with product_ids, so passing both narrows to those SKUs rather than widening. | |
Profile Ids (string) or Profile Ids (null) (Profile Ids) Comma-separated creator profile IDs. | |
Automation Ids (string) or Automation Ids (null) (Automation Ids) Comma-separated automation IDs that sourced the request. | |
Tags (string) or Tags (null) (Tags) Comma-separated CRM tags. | |
Created From (string) or Created From (null) (Created From) Earliest request date, YYYY-MM-DD (inclusive). | |
Created To (string) or Created To (null) (Created To) Latest request date, YYYY-MM-DD (inclusive). | |
Search (string) or Search (null) (Search) Free-text search over creator handle / name. | |
| sort_by | string (Sort By) Default: "created_at" Sort field. |
| sort_direction | string (Sort Direction) Default: "desc" 'asc' or 'desc'. |
| exclude_rejected | boolean (Exclude Rejected) Default: false Drop rejected requests. |
| show_archived | boolean (Show Archived) Default: false Return archived requests instead. |
| show_expiring | boolean (Show Expiring) Default: false Only requests expiring soon. |
Min Post Rate (number) or Min Post Rate (null) (Min Post Rate) | |
Max Post Rate (number) or Max Post Rate (null) (Max Post Rate) | |
Min Pps Score (number) or Min Pps Score (null) (Min Pps Score) | |
Max Pps Score (number) or Max Pps Score (null) (Max Pps Score) | |
Min Avg Views (number) or Min Avg Views (null) (Min Avg Views) | |
Max Avg Views (number) or Max Avg Views (null) (Max Avg Views) | |
Min Engagement Rate (number) or Min Engagement Rate (null) (Min Engagement Rate) | |
Max Engagement Rate (number) or Max Engagement Rate (null) (Max Engagement Rate) | |
Min Live Gmv (number) or Min Live Gmv (null) (Min Live Gmv) | |
Max Live Gmv (number) or Max Live Gmv (null) (Max Live Gmv) | |
Min Shop Gmv (number) or Min Shop Gmv (null) (Min Shop Gmv) | |
Max Shop Gmv (number) or Max Shop Gmv (null) (Max Shop Gmv) | |
Min Creator Gmv (number) or Min Creator Gmv (null) (Min Creator Gmv) | |
Max Creator Gmv (number) or Max Creator Gmv (null) (Max Creator Gmv) | |
Min Avg Gmv Video (number) or Min Avg Gmv Video (null) (Min Avg Gmv Video) | |
Max Avg Gmv Video (number) or Max Avg Gmv Video (null) (Max Avg Gmv Video) | |
Min Gmv Sample (number) or Min Gmv Sample (null) (Min Gmv Sample) | |
Max Gmv Sample (number) or Max Gmv Sample (null) (Max Gmv Sample) | |
Min Videos Per Sample (number) or Min Videos Per Sample (null) (Min Videos Per Sample) | |
Max Videos Per Sample (number) or Max Videos Per Sample (null) (Max Videos Per Sample) | |
Sample Type (string) or Sample Type (null) (Sample Type) Filter by product sample type: 'refundable', 'free', or 'all' (default). |
{- "data": [
- {
- "creator": {
- "id": "string",
- "handle": "string",
- "name": "string",
- "creatorImage": "string",
- "followers": 0,
- "creatorLevel": "string",
- "fastGrowing": true,
- "isRisingStar": true,
- "returning": true,
- "tags": [
- "string"
]
}, - "products": [
- {
- "applyId": "string",
- "productId": "string",
- "name": "string",
- "sku": "string",
- "variantName": "string",
- "productImage": "string",
- "status": "string",
- "qty": 1,
- "isRepeatedRequest": true,
- "isArchived": false,
- "expiresInDays": 0,
- "commissionRate": 0,
- "stock": 0,
- "sampleAllowanceLeft": 0,
- "requestedAt": "string",
- "expiresAt": "string",
- "isExpired": false
}
], - "aggregated": {
- "unfulfilled": 0,
- "postRate": 0,
- "avgViews": 0,
- "ppsScore": 0,
- "liveGmv": 0,
- "engagementRate": 0,
- "shopGmv": 0,
- "creatorGmv": 0,
- "avgGmvPerVideo": 0,
- "gmvPerSample": 0,
- "videosPerSample": 0
}, - "productCount": 0,
- "requestDate": "string",
- "expiresInDays": 0,
- "source": "string"
}
], - "pagination": {
- "page": 0,
- "perPage": 0,
- "totalItems": 0,
- "totalPages": 0
}, - "archivedCount": {
- "creators": 0,
- "products": 0,
- "total": 0
}, - "expiringCount": 0,
- "lastUpdatedAt": "string",
- "dataFreshness": {
- "lastSyncedAt": "string",
- "isStale": false,
- "isRefreshing": false
}
}How many sample requests sit in each pipeline tab — toReview, readyToShip, shipped, pendingContent, completed, cancelled — under the filters you pass, plus the overall completionRate, the shop's approvalTarget (limit and used), archivedCount, expiringCount and a dataFreshness stamp. This is the tab-badge row above the board.
Every tab count INCLUDES requests hidden because the creator is archived, so the funnel matches TikTok's Affiliate Center. archivedCounts breaks that hidden share down per tab, and archivedCount.total is the hidden request count overall. With show_archived=true the archived rows are counted as visible, so archivedCounts comes back zeroed; to list them, pass the same flag to /funnel/v2/requests.
Accepts the same filters as /funnel/v2/requests (the counts respect them), so you can ask 'how many are in each stage for this product / date range'. Single-shop, CRM v2 shops only.
| page | integer (Page) >= 1 Default: 1 Page number, 1-based. |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 Rows per page (max 100). |
Status (string) or Status (null) (Status) Pipeline tab to filter to. One of: toReview, readyToShip, shipped, pendingContent, completed, cancelled. | |
Product Ids (string) or Product Ids (null) (Product Ids) Comma-separated product IDs. | |
Sku Ids (string) or Sku Ids (null) (Sku Ids) Comma-separated SKU IDs. AND-ed with product_ids, so passing both narrows to those SKUs rather than widening. | |
Profile Ids (string) or Profile Ids (null) (Profile Ids) Comma-separated creator profile IDs. | |
Automation Ids (string) or Automation Ids (null) (Automation Ids) Comma-separated automation IDs that sourced the request. | |
Tags (string) or Tags (null) (Tags) Comma-separated CRM tags. | |
Created From (string) or Created From (null) (Created From) Earliest request date, YYYY-MM-DD (inclusive). | |
Created To (string) or Created To (null) (Created To) Latest request date, YYYY-MM-DD (inclusive). | |
Search (string) or Search (null) (Search) Free-text search over creator handle / name. | |
| sort_by | string (Sort By) Default: "created_at" Sort field. |
| sort_direction | string (Sort Direction) Default: "desc" 'asc' or 'desc'. |
| exclude_rejected | boolean (Exclude Rejected) Default: false Drop rejected requests. |
| show_archived | boolean (Show Archived) Default: false Return archived requests instead. |
| show_expiring | boolean (Show Expiring) Default: false Only requests expiring soon. |
Min Post Rate (number) or Min Post Rate (null) (Min Post Rate) | |
Max Post Rate (number) or Max Post Rate (null) (Max Post Rate) | |
Min Pps Score (number) or Min Pps Score (null) (Min Pps Score) | |
Max Pps Score (number) or Max Pps Score (null) (Max Pps Score) | |
Min Avg Views (number) or Min Avg Views (null) (Min Avg Views) | |
Max Avg Views (number) or Max Avg Views (null) (Max Avg Views) | |
Min Engagement Rate (number) or Min Engagement Rate (null) (Min Engagement Rate) | |
Max Engagement Rate (number) or Max Engagement Rate (null) (Max Engagement Rate) | |
Min Live Gmv (number) or Min Live Gmv (null) (Min Live Gmv) | |
Max Live Gmv (number) or Max Live Gmv (null) (Max Live Gmv) | |
Min Shop Gmv (number) or Min Shop Gmv (null) (Min Shop Gmv) | |
Max Shop Gmv (number) or Max Shop Gmv (null) (Max Shop Gmv) | |
Min Creator Gmv (number) or Min Creator Gmv (null) (Min Creator Gmv) | |
Max Creator Gmv (number) or Max Creator Gmv (null) (Max Creator Gmv) | |
Min Avg Gmv Video (number) or Min Avg Gmv Video (null) (Min Avg Gmv Video) | |
Max Avg Gmv Video (number) or Max Avg Gmv Video (null) (Max Avg Gmv Video) | |
Min Gmv Sample (number) or Min Gmv Sample (null) (Min Gmv Sample) | |
Max Gmv Sample (number) or Max Gmv Sample (null) (Max Gmv Sample) | |
Min Videos Per Sample (number) or Min Videos Per Sample (null) (Min Videos Per Sample) | |
Max Videos Per Sample (number) or Max Videos Per Sample (null) (Max Videos Per Sample) | |
Sample Type (string) or Sample Type (null) (Sample Type) Filter by product sample type: 'refundable', 'free', or 'all' (default). |
{- "counts": {
- "toReview": 0,
- "readyToShip": 0,
- "shipped": 0,
- "pendingContent": 0,
- "completed": 0,
- "cancelled": 0,
- "all": 0,
- "inShipment": 0,
- "posted": 0,
- "allApproved": 0,
- "overdue": 0,
- "rejected": 0,
- "expired": 0,
- "ignored": 0
}, - "archivedCounts": {
- "toReview": 0,
- "readyToShip": 0,
- "shipped": 0,
- "pendingContent": 0,
- "completed": 0,
- "cancelled": 0,
- "all": 0,
- "inShipment": 0,
- "posted": 0,
- "allApproved": 0,
- "overdue": 0,
- "rejected": 0,
- "expired": 0,
- "ignored": 0
}, - "completionRate": 0,
- "approvalTarget": {
- "limit": 0,
- "used": 0
}, - "archivedCount": {
- "creators": 0,
- "products": 0,
- "total": 0
}, - "expiringCount": 0,
- "dataFreshness": {
- "lastSyncedAt": "string",
- "isStale": false,
- "isRefreshing": false
}
}The headline metrics above the Sample Requests board: pendingReview (count plus how many expire soon), approvedInAutoProcess (count with period-over-period changePct and changeDirection), completionRate, and approvalTarget progress against the shop's sample-approval limit, with a dataFreshness stamp.
pendingReview.count INCLUDES requests hidden because the creator is archived, so it matches TikTok's Affiliate Center; expiringSoon and completionRate stay over the visible rows, which are the ones you can act on.
Accepts the same filters as /funnel/v2/requests. Under a sku_ids filter, notScopableBySku names the metrics that could not be narrowed — approvalTarget, whose limit and used counters are product-grain — so treat those as unscoped rather than filtered. Single-shop, CRM v2 shops only.
| page | integer (Page) >= 1 Default: 1 Page number, 1-based. |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 Rows per page (max 100). |
Status (string) or Status (null) (Status) Pipeline tab to filter to. One of: toReview, readyToShip, shipped, pendingContent, completed, cancelled. | |
Product Ids (string) or Product Ids (null) (Product Ids) Comma-separated product IDs. | |
Sku Ids (string) or Sku Ids (null) (Sku Ids) Comma-separated SKU IDs. AND-ed with product_ids, so passing both narrows to those SKUs rather than widening. | |
Profile Ids (string) or Profile Ids (null) (Profile Ids) Comma-separated creator profile IDs. | |
Automation Ids (string) or Automation Ids (null) (Automation Ids) Comma-separated automation IDs that sourced the request. | |
Tags (string) or Tags (null) (Tags) Comma-separated CRM tags. | |
Created From (string) or Created From (null) (Created From) Earliest request date, YYYY-MM-DD (inclusive). | |
Created To (string) or Created To (null) (Created To) Latest request date, YYYY-MM-DD (inclusive). | |
Search (string) or Search (null) (Search) Free-text search over creator handle / name. | |
| sort_by | string (Sort By) Default: "created_at" Sort field. |
| sort_direction | string (Sort Direction) Default: "desc" 'asc' or 'desc'. |
| exclude_rejected | boolean (Exclude Rejected) Default: false Drop rejected requests. |
| show_archived | boolean (Show Archived) Default: false Return archived requests instead. |
| show_expiring | boolean (Show Expiring) Default: false Only requests expiring soon. |
Min Post Rate (number) or Min Post Rate (null) (Min Post Rate) | |
Max Post Rate (number) or Max Post Rate (null) (Max Post Rate) | |
Min Pps Score (number) or Min Pps Score (null) (Min Pps Score) | |
Max Pps Score (number) or Max Pps Score (null) (Max Pps Score) | |
Min Avg Views (number) or Min Avg Views (null) (Min Avg Views) | |
Max Avg Views (number) or Max Avg Views (null) (Max Avg Views) | |
Min Engagement Rate (number) or Min Engagement Rate (null) (Min Engagement Rate) | |
Max Engagement Rate (number) or Max Engagement Rate (null) (Max Engagement Rate) | |
Min Live Gmv (number) or Min Live Gmv (null) (Min Live Gmv) | |
Max Live Gmv (number) or Max Live Gmv (null) (Max Live Gmv) | |
Min Shop Gmv (number) or Min Shop Gmv (null) (Min Shop Gmv) | |
Max Shop Gmv (number) or Max Shop Gmv (null) (Max Shop Gmv) | |
Min Creator Gmv (number) or Min Creator Gmv (null) (Min Creator Gmv) | |
Max Creator Gmv (number) or Max Creator Gmv (null) (Max Creator Gmv) | |
Min Avg Gmv Video (number) or Min Avg Gmv Video (null) (Min Avg Gmv Video) | |
Max Avg Gmv Video (number) or Max Avg Gmv Video (null) (Max Avg Gmv Video) | |
Min Gmv Sample (number) or Min Gmv Sample (null) (Min Gmv Sample) | |
Max Gmv Sample (number) or Max Gmv Sample (null) (Max Gmv Sample) | |
Min Videos Per Sample (number) or Min Videos Per Sample (null) (Min Videos Per Sample) | |
Max Videos Per Sample (number) or Max Videos Per Sample (null) (Max Videos Per Sample) | |
Sample Type (string) or Sample Type (null) (Sample Type) Filter by product sample type: 'refundable', 'free', or 'all' (default). |
{- "pendingReview": {
- "count": 0,
- "expiringSoon": 0
}, - "approvedInAutoProcess": {
- "count": 0,
- "changePct": 0,
- "changeDirection": "up"
}, - "completionRate": {
- "rate": 0,
- "changePct": 0,
- "changeDirection": "up"
}, - "approvalTarget": {
- "monthlyLimit": 0,
- "monthlyUsed": 0,
- "dailyTarget": 0,
- "dailyCurrent": 0,
- "dailyPct": 0,
- "onTrack": true
}, - "dataFreshness": {
- "lastSyncedAt": "string",
- "isStale": false,
- "isRefreshing": false
}, - "notScopableBySku": [
- "string"
]
}The values you can actually filter the CRM v2 pipeline by for this shop — the products, sourcing automations, CRM tags and status tabs that exist in its data, plus the bounds of each metric range. Call this once to build a filter UI or to discover valid product_ids / automation_ids / tags before querying /funnel/v2/requests.
Single-shop, CRM v2 shops only.
| page | integer (Page) >= 1 Default: 1 Page number, 1-based. |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 Rows per page (max 100). |
Status (string) or Status (null) (Status) Pipeline tab to filter to. One of: toReview, readyToShip, shipped, pendingContent, completed, cancelled. | |
Product Ids (string) or Product Ids (null) (Product Ids) Comma-separated product IDs. | |
Sku Ids (string) or Sku Ids (null) (Sku Ids) Comma-separated SKU IDs. AND-ed with product_ids, so passing both narrows to those SKUs rather than widening. | |
Profile Ids (string) or Profile Ids (null) (Profile Ids) Comma-separated creator profile IDs. | |
Automation Ids (string) or Automation Ids (null) (Automation Ids) Comma-separated automation IDs that sourced the request. | |
Tags (string) or Tags (null) (Tags) Comma-separated CRM tags. | |
Created From (string) or Created From (null) (Created From) Earliest request date, YYYY-MM-DD (inclusive). | |
Created To (string) or Created To (null) (Created To) Latest request date, YYYY-MM-DD (inclusive). | |
Search (string) or Search (null) (Search) Free-text search over creator handle / name. | |
| sort_by | string (Sort By) Default: "created_at" Sort field. |
| sort_direction | string (Sort Direction) Default: "desc" 'asc' or 'desc'. |
| exclude_rejected | boolean (Exclude Rejected) Default: false Drop rejected requests. |
| show_archived | boolean (Show Archived) Default: false Return archived requests instead. |
| show_expiring | boolean (Show Expiring) Default: false Only requests expiring soon. |
Min Post Rate (number) or Min Post Rate (null) (Min Post Rate) | |
Max Post Rate (number) or Max Post Rate (null) (Max Post Rate) | |
Min Pps Score (number) or Min Pps Score (null) (Min Pps Score) | |
Max Pps Score (number) or Max Pps Score (null) (Max Pps Score) | |
Min Avg Views (number) or Min Avg Views (null) (Min Avg Views) | |
Max Avg Views (number) or Max Avg Views (null) (Max Avg Views) | |
Min Engagement Rate (number) or Min Engagement Rate (null) (Min Engagement Rate) | |
Max Engagement Rate (number) or Max Engagement Rate (null) (Max Engagement Rate) | |
Min Live Gmv (number) or Min Live Gmv (null) (Min Live Gmv) | |
Max Live Gmv (number) or Max Live Gmv (null) (Max Live Gmv) | |
Min Shop Gmv (number) or Min Shop Gmv (null) (Min Shop Gmv) | |
Max Shop Gmv (number) or Max Shop Gmv (null) (Max Shop Gmv) | |
Min Creator Gmv (number) or Min Creator Gmv (null) (Min Creator Gmv) | |
Max Creator Gmv (number) or Max Creator Gmv (null) (Max Creator Gmv) | |
Min Avg Gmv Video (number) or Min Avg Gmv Video (null) (Min Avg Gmv Video) | |
Max Avg Gmv Video (number) or Max Avg Gmv Video (null) (Max Avg Gmv Video) | |
Min Gmv Sample (number) or Min Gmv Sample (null) (Min Gmv Sample) | |
Max Gmv Sample (number) or Max Gmv Sample (null) (Max Gmv Sample) | |
Min Videos Per Sample (number) or Min Videos Per Sample (null) (Min Videos Per Sample) | |
Max Videos Per Sample (number) or Max Videos Per Sample (null) (Max Videos Per Sample) | |
Sample Type (string) or Sample Type (null) (Sample Type) Filter by product sample type: 'refundable', 'free', or 'all' (default). |
{- "creatorNames": [
- "string"
], - "status": [
- "string"
], - "followerCount": [
- "string"
], - "shopGMV": [
- "string"
], - "creatorGMV": [
- "string"
], - "creatorFulfillmentRate": [
- "string"
], - "products": [
- {
- "product_id": "string",
- "product_title": "string",
- "product_image": "string",
- "skus": [
- {
- "sku_id": "string",
- "sku_name": "string",
- "sku_image": "string",
- "sku_left_num": 0
}
]
}
], - "gender": [
- "string"
], - "tags": [
- "string"
], - "videosPosted": [
- "string"
], - "videoPublishDate": [
- "string"
], - "avgVideoViews": [
- "string"
], - "videoGMV": [
- "string"
], - "totalVideoViews": [
- "string"
]
}Reacher Recommends for one shop: the automation types the shop has not run in the current timeframe, returned as ready-to-action suggestions. Each entry carries the automation title, description, impact type (e.g. 'High Impact', 'Addon'), the navigate path to set it up, its button label, and shop_names — the shops the recommendation applies to.
Covers the four automation plays Reacher scores: Target Collab, Message + Product Card, Spark Code, and Content Flywheel. An empty array means the shop is already running all of them. Single-shop only.
nullThe performance targets the shop is measured against — the goal values behind the dashboard's progress bars. Each target carries its granularity (weekly / monthly / quarterly), the period it applies from (created_at), and a targets object with the per-metric goals: target_collabs, creators_messaged, sample_requests, sample_approved, videos_posted, videos_converted, spark_codes.
Omit granularity to get the latest target for each granularity the shop has set; pass one to get that granularity's history, newest first. An empty targets array means the shop has not set any.
Read-only — targets are created and edited in the Reacher dashboard. Single-shop only.
Granularity (string) or Granularity (null) (Granularity) Restrict to one granularity: weekly, monthly, quarterly. Omit for the latest of each. |
{- "targets": [
- {
- "created_at": "2024-01-01T00:00:00Z",
- "creators_messaged": 200,
- "customer_id": 1,
- "granularity": "monthly",
- "id": "550e8400-e29b-41d4-a716-446655440000",
- "sample_approved": 80,
- "sample_requests": 100,
- "shop_id": 123,
- "spark_codes": 10,
- "target_collabs": 50,
- "updated_at": "2024-01-01T00:00:00Z",
- "videos_converted": 25
}
], - "total_count": 1
}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.
nullList 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).
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 5000 ] Default: 5000 Campaigns per page (default = max, above today's whole table); check pagination.total_pages for multi-shop keys. |
{- "data": [
- {
- "shop_id": 0,
- "campaign_id": "string",
- "campaign_name": "string",
- "status": "ENABLE",
- "shopping_ads_type": "PRODUCT",
- "budget": 0,
- "target_roas": 0,
- "roas_bid": 0,
- "last_synced_at": "string",
- "currency": "string"
}
], - "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0
}, - "currency": "string",
- "shops_queried": [
- "string"
]
}Create a new GMV Max (TikTok Smart+) campaign on the shop's connected TikTok Business ad account. Requires read_write scope. Single shop only — set x-shop-id to the owning shop.
The canonical body is mapped to TikTok's create payload server-side (daily_budget→budget, target_roas→ROAS bid, product_selection→ALL / CUSTOMIZED_PRODUCTS, etc.). The shop must already be connected to TikTok Business / GMV Max — an unconnected or incomplete connection returns 400 INVALID_REQUEST.
Idempotent via the Idempotency-Key header — replays of the same key + same body return the cached 201 without creating a duplicate. X-Dry-Run: true validates the body + connection and echoes the exact TikTok payload without calling TikTok.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) | |
X-Created-Via (string) or X-Created-Via (null) (X-Created-Via) |
| campaign_name required | string (Campaign Name) [ 1 .. 255 ] characters Human-readable campaign name shown in Seller Center / the portal. |
| campaign_type | string (Campaign Type) Default: "product" Enum: "product" "live"
|
| product_selection required | string (Product Selection) Enum: "all" "customized"
|
Array of Item Group Ids (strings) or Item Group Ids (null) (Item Group Ids) Product (SPU / item_group_id) IDs to promote. Required and non-empty when | |
| target_roas required | number (Target Roas) > 0 Target ROAS. Required — TikTok GMV Max create always needs a target ROI: maps to TikTok |
| daily_budget required | number (Daily Budget) > 0 Daily budget in the shop's currency. Maps to TikTok |
| schedule_type required | string (Schedule Type) Enum: "from_now" "start_end"
|
| schedule_start_time required | string (Schedule Start Time) Campaign start time, TikTok format |
Schedule End Time (string) or Schedule End Time (null) (Schedule End Time) Campaign end time, TikTok format | |
Promotion Days (integer) or Promotion Days (null) (Promotion Days) Optional number of promotion days (LIVE GMV Max scheduling hint). | |
Auto Budget Enabled (boolean) or Auto Budget Enabled (null) (Auto Budget Enabled) Optional: let TikTok auto-manage the budget. | |
Affiliate Posts Enabled (boolean) or Affiliate Posts Enabled (null) (Affiliate Posts Enabled) Optional: allow affiliate creator posts to be used as creatives. |
{- "campaign_name": "string",
- "campaign_type": "product",
- "product_selection": "all",
- "item_group_ids": [
- "string"
], - "target_roas": 0,
- "daily_budget": 0,
- "schedule_type": "from_now",
- "schedule_start_time": "string",
- "schedule_end_time": "string",
- "promotion_days": 1,
- "auto_budget_enabled": true,
- "affiliate_posts_enabled": true
}nullDaily 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.
| campaign_id required | string (Campaign Id) [ 1 .. 64 ] characters |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) |
{- "campaign_id": "string",
- "shop_id": 0,
- "data": [
- {
- "date": "string",
- "spend": 0,
- "impressions": 0,
- "clicks": 0,
- "orders": 0,
- "gross_revenue": 0,
- "cpc": 0,
- "cpm": 0,
- "ctr": 0,
- "roas": 0,
- "ad_roi": 0
}
], - "start_date": "string",
- "end_date": "string",
- "currency": "string"
}Daily per-creative performance for one campaign, including the video view-rate funnel: 2-second and 6-second view rates plus watch-completion rates (25/50/75/100%), all as percentages (0–100), alongside spend, impressions, clicks, conversions, revenue and ROI. Returns one row per creative per day (raw daily series — aggregate as needed). Optionally narrow to a single creative with material_id. Date range may not exceed 90 days (inclusive); defaults to the last 30 days. Multi-shop is supported — the campaign's owning shop is resolved automatically from x-shop-id.
| campaign_id required | string (Campaign Id) [ 1 .. 64 ] characters |
Material Id (string) or Material Id (null) (Material Id) Optional: restrict to a single creative (material) id. | |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) |
{- "campaign_id": "string",
- "shop_id": 0,
- "data": [
- {
- "material_id": "string",
- "date": "string",
- "spend": 0,
- "impressions": 0,
- "clicks": 0,
- "conversions": 0,
- "gross_revenue": 0,
- "revenue": 0,
- "ad_roi": 0,
- "roi": 0,
- "video_view_rate_2s": 0,
- "video_view_rate_6s": 0,
- "cost_per_order": 0,
- "ctr": 0,
- "video_ctr": 0,
- "conversion_rate": 0,
- "item_group_id": "string",
- "delivery_status": "string",
- "surface": "string",
- "video_view_rate_p25": 0,
- "video_view_rate_p50": 0,
- "video_view_rate_p75": 0,
- "video_view_rate_p100": 0
}
], - "start_date": "string",
- "end_date": "string",
- "currency": "string"
}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.
| days | integer (Days) [ 1 .. 90 ] Default: 7 Window size in days (default 7). |
nullDetail 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.
| campaign_id required | string (Campaign Id) [ 1 .. 64 ] characters |
{- "campaign_id": "string",
- "campaign_name": "string",
- "status": "ENABLE",
- "shopping_ads_type": "PRODUCT",
- "budget": 0,
- "target_roas": 0,
- "roas_bid": 0,
- "last_synced_at": "string",
- "currency": "string",
- "raw_data": { },
- "daily_metrics": [
- {
- "date": "string",
- "spend": 0,
- "impressions": 0,
- "clicks": 0,
- "orders": 0,
- "gross_revenue": 0,
- "cpc": 0,
- "cpm": 0,
- "ctr": 0,
- "roas": 0,
- "ad_roi": 0
}
]
}Patch the mutable subset of a GMV Max campaign — daily budget, target ROAS, name, and pause/resume (operation_status). Requires read_write scope. PATCH semantics — fields the caller omits are left unchanged; at least one field must be provided.
The shop must be connected to TikTok Business / GMV Max (400 INVALID_REQUEST otherwise). Idempotent via the Idempotency-Key header. X-Dry-Run: true echoes the mapped TikTok patch without calling TikTok. Single shop only.
| campaign_id required | string (Campaign Id) [ 1 .. 64 ] characters |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) | |
X-Created-Via (string) or X-Created-Via (null) (X-Created-Via) |
Campaign Name (string) or Campaign Name (null) (Campaign Name) | |
Daily Budget (number) or Daily Budget (null) (Daily Budget) New daily budget. Maps to TikTok | |
Target Roas (number) or Target Roas (null) (Target Roas) New target ROAS. Maps to TikTok | |
Operation Status (string) or Operation Status (null) (Operation Status) Pause ( | |
Promotion Days (integer) or Promotion Days (null) (Promotion Days) | |
Auto Budget Enabled (boolean) or Auto Budget Enabled (null) (Auto Budget Enabled) |
{- "campaign_name": "string",
- "daily_budget": 0,
- "target_roas": 0,
- "operation_status": "ENABLE",
- "promotion_days": 1,
- "auto_budget_enabled": true
}nullList the synced creatives (videos, spark codes, etc.) attached to a specific GMV Max campaign, with per-creative performance summaries. Single shop only.
| campaign_id required | string (Campaign Id) [ 1 .. 64 ] characters GMV Max campaign ID. |
nullCached 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.
nullHard-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.
nullList 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.
nullCreate 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.
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.
Every 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.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| name required | string (Name) [ 1 .. 255 ] characters |
Template Type (string) or Template Type (null) (Template Type) Optional reference to one of the | |
| trigger_frequency | string (TriggerFrequency) Default: "daily" Value: "daily" How often the automation evaluates. Only |
| campaign_ids | Array of strings (Campaign Ids) 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. |
Array of objects (Conditions) | |
Array of objects (Actions) | |
Array of objects (Guardrails) | |
Boost Start Date (string) or Boost Start Date (null) (Boost Start Date) First day boosts may fire. Must be paired with | |
Boost End Date (string) or Boost End Date (null) (Boost End Date) |
{- "name": "string",
- "template_type": "string",
- "trigger_frequency": "daily",
- "campaign_ids": [
- "string"
], - "conditions": [
- {
- "metric": "creative_roi",
- "operator": "lt",
- "value": 0,
- "lookback_days": 3
}
], - "actions": [
- {
- "action_type": "set_up_creative_boost",
- "params": { }
}
], - "guardrails": [
- {
- "rule_type": "total_creative_boost_cost",
- "operator": "lt",
- "value": 0
}
], - "boost_start_date": "2019-08-24",
- "boost_end_date": "2019-08-24"
}nullDetail for one automation: full configuration (conditions, actions, guardrails) plus its 10 most recent runs. Single shop only — automation_ids are scoped per shop.
| automation_id required | integer (Automation Id) >= 1 |
nullPatch 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.
PATCH 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).
X-Dry-Run: true echoes the patch without writing. Idempotent via the Idempotency-Key header. Single shop only.
| automation_id required | integer (Automation Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
Name (string) or Name (null) (Name) | |
TriggerFrequency (string) or null | |
Array of Campaign Ids (strings) or Campaign Ids (null) (Campaign Ids) | |
Array of Conditions (objects) or Conditions (null) (Conditions) | |
Array of Actions (objects) or Actions (null) (Actions) | |
Array of Guardrails (objects) or Guardrails (null) (Guardrails) | |
Boost Start Date (string) or Boost Start Date (null) (Boost Start Date) | |
Boost End Date (string) or Boost End Date (null) (Boost End Date) |
{- "name": "string",
- "trigger_frequency": "daily",
- "campaign_ids": [
- "string"
], - "conditions": [
- {
- "metric": "creative_roi",
- "operator": "lt",
- "value": 0,
- "lookback_days": 3
}
], - "actions": [
- {
- "action_type": "set_up_creative_boost",
- "params": { }
}
], - "guardrails": [
- {
- "rule_type": "total_creative_boost_cost",
- "operator": "lt",
- "value": 0
}
], - "boost_start_date": "2019-08-24",
- "boost_end_date": "2019-08-24"
}nullSoft-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).
Pending boost rows queued by this automation are cancelled in the background — best-effort, not awaited here. Single shop only.
| automation_id required | integer (Automation Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
nullFull 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.
| automation_id required | integer (Automation Id) >= 1 |
nullPaginated 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.
| automation_id required | integer (Automation Id) >= 1 |
Status (string) or Status (null) (Status) Filter by activity status (matches portal). | |
Search (string) or Search (null) (Search) Free-text search across activity rows. | |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
nullMark an automation status='stopped' (terminal — no resume). Already-stopped automations are returned unchanged (idempotent on the row itself, independent of the Idempotency-Key header).
Boost 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.
| automation_id required | integer (Automation Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
nullSynchronously authorise the listed spark codes against the shop's TikTok Business ad account. Each code fires one TikTok /tt_video/authorize/ call:
sync_status flips to synced.pending so the offline CRJ retries it; the response counts this in queued.failed with the error string.Up to 100 codes per request. Shops without an active TikTok Business connection return 422 TIKTOK_NOT_CONNECTED. Single shop only.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| spark_codes required | Array of strings (Spark Codes) [ 1 .. 100 ] items Up to 100 spark codes to sync in one request. Each code fires one TikTok |
{- "spark_codes": [
- "string"
]
}nullQueue 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.
Returns immediately with {queued, skipped, message}. Shops without an active TikTok Business connection return 422 TIKTOK_NOT_CONNECTED. Single shop only.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
POST /public/v1/gmv-max/spark-codes/sync-all — queue every
unsynced code for the shop. Returns immediately with a queue count;
the offline CRJ actually fires the TikTok calls.
Has no body fields (shop_id comes from x-shop-id); kept as a model
so we can still extra="forbid" against accidental payloads.
{ }nullDaily GMV Max metrics broken down to the product (SPU / item_group_id) level for one shop — cost, gross revenue (GMV), SKU orders, ROI, and cost-per-order per product per day. This is the grain behind TikTok Seller Center's per-campaign Product table.
Optionally filter to a single campaign_id and/or item_group_id. item_group_id (aliased as product_id) is the join key to the product / Seller Center product datasets.
Single-shop only — set x-shop-id to a specific shop ID. Defaults to the last 30 days ending yesterday; max range 90 days. Empty data is a normal 200 (a shop may have no GMV Max campaigns).
roi and cost_per_order are TikTok's stored daily values — for window totals use /gmv-max/products/summary, which recomputes them from the summed bases.
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). Defaults to 29 days before | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). Defaults to yesterday (today's row is still incomplete). | |
Campaign Id (string) or Campaign Id (null) (Campaign Id) Restrict to a single GMV Max campaign. | |
Item Group Id (string) or Item Group Id (null) (Item Group Id) Restrict to a single product (TikTok SPU / item_group_id). |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "campaign_id": "string",
- "item_group_id": "string"
}{- "shop_id": 0,
- "currency": "string",
- "start_date": "string",
- "end_date": "string",
- "data": [
- {
- "date": "string",
- "campaign_id": "string",
- "item_group_id": "string",
- "product_id": "string",
- "spend": 0,
- "cost": 0,
- "gross_revenue": 0,
- "orders": 0,
- "roi": 0,
- "ad_roi": 0,
- "cost_per_order": 0
}
]
}GMV Max ad spend and performance at the video (creative) grain for one shop — one row per TikTok video across every campaign it delivered in during the window: spend, orders, cost per order, ROI, revenue, impressions, clicks, CTR (ad click rate), video CTR (product click rate), latest delivery status, surface, the video's posted date and creator when it is an affiliate video, and whether it is already on the shop's exclusion list. This is the per-creative export a media buyer pulls from Ads Manager by hand.
Totals are SUMs over the window; cost_per_order and roi are recomputed from the summed bases; ctr / video_ctr are impressions-weighted. Filter with campaign_id and min_spend (e.g. min_spend: 10 with orders == 0 to find creatives to exclude), sort with sort_by / sort_dir, page with page / page_size (max 200). Exclude a video with POST /gmv-max/excluded-videos (or /bulk).
Single-shop only. Defaults to the last 30 days ending yesterday; max range 90 days. Empty data is a normal 200.
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). Defaults to 29 days before | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). Defaults to yesterday. | |
Campaign Id (string) or Campaign Id (null) (Campaign Id) Restrict to a single GMV Max campaign. | |
Min Spend (number) or Min Spend (null) (Min Spend) Only videos whose window spend is at least this amount. | |
| sort_by | string (Sort By) Default: "spend" Enum: "spend" "revenue" "orders" "roi" "cost_per_order" "ctr" "video_ctr" "last_active_date" Column to sort by. |
| sort_dir | string (Sort Dir) Default: "desc" Enum: "asc" "desc" |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 200 ] Default: 50 |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "campaign_id": "string",
- "min_spend": 0,
- "sort_by": "spend",
- "sort_dir": "asc",
- "page": 1,
- "page_size": 50
}{- "shop_id": 0,
- "currency": "string",
- "start_date": "string",
- "end_date": "string",
- "data": [
- {
- "video_id": "string",
- "campaign_ids": [
- "string"
], - "item_group_id": "string",
- "posted_date": "string",
- "creator_name": "string",
- "title": "string",
- "spend": 0,
- "revenue": 0,
- "orders": 0,
- "cost_per_order": 0,
- "roi": 0,
- "impressions": 0,
- "clicks": 0,
- "ctr": 0,
- "video_ctr": 0,
- "delivery_status": "string",
- "surface": "string",
- "first_active_date": "string",
- "last_active_date": "string",
- "is_excluded": true,
- "exclusion_status": "string"
}
], - "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0
}
}Per-product window totals for one shop, plus an account-level overview — the Seller Center GMV Max Ads overview card (total cost, SKU orders, cost per order, gross revenue, ROI).
Products carry per-product cost + GMV + SKU orders (SUMmed over the window) with cost_per_order and roi recomputed from the summed bases, and the owning campaign's name / status / budget / target ROI (= ROAS bid) joined in. Sorted by cost descending.
Overview is derived from the same per-product sums, so it reconciles with the product rows. (Note: product-level cost can differ slightly from the campaign-metrics cost — TikTok attributes them differently; CORE-4486.)
Single-shop only. Same window defaults/caps as the timeseries endpoint. Optionally filter to a single campaign_id.
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). Defaults to 29 days before | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). Defaults to yesterday. | |
Campaign Id (string) or Campaign Id (null) (Campaign Id) Restrict to a single GMV Max campaign. |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "campaign_id": "string"
}{- "shop_id": 0,
- "currency": "string",
- "start_date": "string",
- "end_date": "string",
- "overview": {
- "spend": 0,
- "cost": 0,
- "gross_revenue": 0,
- "orders": 0,
- "cost_per_order": 0,
- "roi": 0,
- "ad_roi": 0
}, - "products": [
- {
- "campaign_id": "string",
- "campaign_name": "string",
- "status": "ENABLE",
- "budget": 0,
- "target_roas": 0,
- "target_roi": 0,
- "item_group_id": "string",
- "product_id": "string",
- "spend": 0,
- "cost": 0,
- "gross_revenue": 0,
- "orders": 0,
- "cost_per_order": 0,
- "roi": 0,
- "ad_roi": 0
}
]
}The campaign's current settings, normalized from TikTok's raw campaign/gmv_max/info payload into a stable snake_case object: status, target_roas, daily_budget, schedule window, auto_budget_enabled, product_selection, roi_protection_enabled, affiliate_posts_enabled, and placements. Settings TikTok did not report are null. Single shop only — set x-shop-id to the campaign's owning shop. 404 if the campaign is not found in this shop.
| campaign_id required | string (Campaign Id) [ 1 .. 64 ] characters |
{- "data": {
- "status": "string",
- "target_roas": 0,
- "daily_budget": 0,
- "schedule_type": "string",
- "schedule_start_time": { },
- "schedule_end_time": { },
- "promotion_days": { },
- "auto_budget_enabled": true,
- "product_selection": { },
- "roi_protection_enabled": true,
- "affiliate_posts_enabled": true,
- "placements": { }
}, - "currency": "string"
}Creative counts and spend for one campaign grouped by delivery status, computed from each creative's LATEST daily metric row. Statuses are bucketed from TikTok's raw creative_delivery_status into a canonical set (delivering, not_delivering, in_review, rejected, inactive, unknown); NULL or unrecognised statuses bucket as unknown. Sorted by spend descending. Empty data is a normal 200. Single shop only.
| campaign_id required | string (Campaign Id) [ 1 .. 64 ] characters |
{- "campaign_id": "string",
- "shop_id": 0,
- "data": [
- {
- "status": "delivering",
- "creative_count": 0,
- "spend": 0
}
], - "currency": "string"
}Ad spend and distinct-creative counts for one campaign grouped by delivery surface (product_card, affiliate, brand, unknown) over a date range. NULL / unrecognised surface_bucket values bucket as unknown. Date range may not exceed 90 days (inclusive); defaults to the last 30 days. Sorted by spend descending. Empty data is a normal 200. Single shop only.
| campaign_id required | string (Campaign Id) [ 1 .. 64 ] characters |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) |
{- "campaign_id": "string",
- "shop_id": 0,
- "data": [
- {
- "surface": "product_card",
- "spend": 0,
- "creative_count": 0
}
], - "start_date": "string",
- "end_date": "string",
- "currency": "string"
}Paginated, append-only log of settings changes for one campaign, newest first. Each row carries the changed field, its old_value and new_value (JSON), and the changed_at timestamp. Empty data is a normal 200. Single shop only.
| campaign_id required | string (Campaign Id) [ 1 .. 64 ] characters |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 50 |
{- "data": [
- {
- "field": "string",
- "old_value": { },
- "new_value": { },
- "changed_at": "string"
}
], - "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0
}
}List the creators excluded from this shop's GMV Max bottom-of-funnel delivery — both manually excluded (source: manual) and auto-detected (source: auto_bof). Ordered applied first, then pending, then failed; newest first within each status. Single shop only (set x-shop-id to a specific shop ID).
{- "data": [
- {
- "handle": "string",
- "source": "string",
- "reason": "string",
- "bof_confidence": 0,
- "excluded_video_count": 0,
- "applied_status": "string",
- "decided_by": "string",
- "created_at": "string",
- "updated_at": "string"
}
]
}Manually exclude a creator (by TikTok handle) from this shop's GMV Max bottom-of-funnel delivery. Idempotent on (shop, handle): re-excluding an already-excluded creator is a no-op upsert that returns the existing row. Requires read_write scope and an Idempotency-Key. Single shop only.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) |
| handle required | string (Handle) [ 1 .. 255 ] characters The creator's TikTok handle to exclude from GMV Max delivery — the same |
{- "handle": "string"
}{- "data": {
- "handle": "string",
- "source": "string",
- "reason": "string",
- "bof_confidence": 0,
- "excluded_video_count": 0,
- "applied_status": "string",
- "decided_by": "string",
- "created_at": "string",
- "updated_at": "string"
}
}Read the shop's GMV Max bottom-of-funnel auto-exclude toggle. Defaults to auto_exclude_enabled: false for shops that have never set it. Single shop only.
{- "data": {
- "shop_id": 0,
- "auto_exclude_enabled": true
}
}Enable or disable automatic exclusion of bottom-of-funnel creators for this shop's GMV Max delivery. Upserts the shop's toggle and returns the new value. Requires read_write scope and an Idempotency-Key. Single shop only.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) |
| auto_exclude_enabled required | boolean (Auto Exclude Enabled) When true, the shop's GMV Max BOF reconcile job automatically excludes creators it flags as bottom-of-funnel. When false, only creators excluded manually (via POST /gmv-max/excluded-creators) are taken off delivery. |
{- "auto_exclude_enabled": true
}{- "data": {
- "shop_id": 0,
- "auto_exclude_enabled": true
}
}Remove a creator from this shop's GMV Max exclusion list, restoring them to bottom-of-funnel delivery. handle is the creator's TikTok handle (from GET /gmv-max/excluded-creators). Removing a creator who isn't excluded is a no-op that still returns 200 with removed: false. Requires read_write scope and an Idempotency-Key. Single shop only.
| handle required | string (Handle) [ 1 .. 255 ] characters The excluded creator's TikTok handle. |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) |
{- "data": {
- "handle": "string",
- "removed": true
}
}List the individual videos (creatives) excluded from this shop's GMV Max delivery. Unlike creator exclusions, a video exclusion takes ONE creative off spend and leaves the creator's other videos live. Ordered applied first, then pending, then failed; newest first within each status. applied_status tells you whether TikTok has confirmed the exclusion (applied), it is waiting for the 4-hourly reconcile (pending), the video is not in any active campaign (no_creatives), or an actively delivering placement could not be excluded (failed). Single shop only.
{- "data": [
- {
- "video_id": "string",
- "source": "string",
- "reason": "string",
- "excluded_campaign_count": 0,
- "applied_status": "string",
- "decided_by": "string",
- "created_at": "string",
- "updated_at": "string"
}
]
}Exclude ONE video (creative) from this shop's GMV Max delivery, by TikTok video_id, without excluding its creator — use this to cut an unprofitable creative (e.g. zero orders at $10+ spend) while the creator's other videos keep delivering. The reconcile job removes the creative from every active campaign it is delivering in within ~4 hours and verifies it flipped to EXCLUDED. Idempotent on (shop, video_id): re-excluding an already excluded video keeps its current applied_status. Requires read_write scope and an Idempotency-Key. Single shop only. For many videos at once use POST /gmv-max/excluded-videos/bulk.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) |
| video_id required | string (Video Id) [ 1 .. 255 ] characters The TikTok video id to exclude — the same |
Reason (string) or Reason (null) (Reason) Optional free-text note (e.g. 'zero orders at $12 spend'). |
{- "video_id": "string",
- "reason": "string"
}{- "data": {
- "video_id": "string",
- "source": "string",
- "reason": "string",
- "excluded_campaign_count": 0,
- "applied_status": "string",
- "decided_by": "string",
- "created_at": "string",
- "updated_at": "string"
}
}Exclude a list of videos (creatives) from this shop's GMV Max delivery in one call — paste the video ids from an Ads Manager export or from the GMV Max video summary tool. Up to 500 valid ids per call; duplicates are collapsed, already-excluded ids are counted in already_excluded, and inputs that are not a numeric TikTok video id come back in invalid (nothing is silently dropped). Each excluded creative comes off spend on the next reconcile (~4h) while its creator's other videos keep delivering. Requires read_write scope and an Idempotency-Key. Single shop only.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) |
| video_ids required | Array of strings (Video Ids) [ 1 .. 500 ] items TikTok video ids to exclude (max 500 per call). Duplicates are collapsed; already-excluded ids are counted, not re-written. |
Reason (string) or Reason (null) (Reason) Optional note stored on every row created by this call. |
{- "video_ids": [
- "string"
], - "reason": "string"
}{- "data": {
- "requested": 0,
- "excluded": 0,
- "already_excluded": 0,
- "invalid": [
- "string"
], - "excluded_video_ids": [
- "string"
]
}
}Remove a video from this shop's GMV Max exclusion list. video_id is the TikTok video id (from GET /gmv-max/excluded-videos). Reacher stops enforcing the exclusion; TikTok does not automatically re-add a creative it has already EXCLUDED, so re-adding it to a campaign is done in Ads Manager. Removing a video that isn't excluded is a no-op that still returns 200 with removed: false. Requires read_write scope and an Idempotency-Key. Single shop only.
| video_id required | string (Video Id) [ 1 .. 255 ] characters The excluded video's TikTok video id. |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) |
{- "data": {
- "video_id": "string",
- "removed": true
}
}Cross-channel Meta Ads rev-share campaigns: campaign settings + creator roster, the per-campaign leaderboard (attributed GMV, ad spend, impressions, commission from the attribution ledger), Stripe creator payouts + history, and the Meta media library. All money is reported in cents in the shop's Meta ad account currency. Single shop only. Distinct from GMV Max (TikTok Smart+ ads) and from Creator Community campaign payments at /campaigns/payments/*.
List the shop's Meta Ads (Cross-channel) rev-share campaigns, newest first, optionally filtered by status. Each row carries its lifecycle settings (commission base/rate, attribution window, usage-rights and whitelist grants) plus lightweight rollups: active_creators, lifetime attributed_revenue_cents, and unpaid commission_owed_cents. Single shop only.
Status (string) or Status (null) (Status) Filter by lifecycle status: draft | active | paused. |
{- "campaigns": [
- {
- "campaign_id": "string",
- "id": "string",
- "short_id": "string",
- "shop_id": 0,
- "name": "string",
- "status": "draft",
- "commission_base": "attributed_revenue",
- "commission_rate": 0,
- "commission_cap_cents": 0,
- "attribution_window": "string",
- "start_date": "string",
- "end_date": "string",
- "usage_grant_on": true,
- "whitelist_grant_on": true,
- "whitelist_path": "spark_code",
- "created_at": "string",
- "updated_at": "string",
- "active_creators": 0,
- "attributed_revenue_cents": 0,
- "commission_owed_cents": 0
}
], - "currency": "string"
}Full detail for one rev-share campaign: its settings plus the creator roster (opt-in status, whitelist grant, resolved TikTok handle/name/avatar per creator). Single shop only — address the campaign's owning shop via x-shop-id.
| campaign_id required | string (Campaign Id) [ 1 .. 64 ] characters |
{- "campaign": {
- "campaign_id": "string",
- "id": "string",
- "short_id": "string",
- "shop_id": 0,
- "name": "string",
- "status": "draft",
- "commission_base": "attributed_revenue",
- "commission_rate": 0,
- "commission_cap_cents": 0,
- "attribution_window": "string",
- "start_date": "string",
- "end_date": "string",
- "usage_grant_on": true,
- "whitelist_grant_on": true,
- "whitelist_path": "spark_code",
- "created_at": "string",
- "updated_at": "string",
- "active_creators": 0,
- "attributed_revenue_cents": 0,
- "commission_owed_cents": 0
}, - "roster": [
- {
- "campaign_id": "string",
- "creator_id": "string",
- "invite_status": "pending",
- "invited_at": "string",
- "accepted_at": "string",
- "activated_at": "string",
- "partnership_ad_code": "string",
- "whitelist_granted": false,
- "creator_handle": "string",
- "creator_name": "string",
- "avatar_url": "string"
}
], - "currency": "string"
}The Creators-tab view for one campaign: five summary-card totals (attributed GMV, impressions/views, GMV per video, ad spend, commission owed) plus a per-creator leaderboard with each creator's attributed GMV, ad spend, impressions, distinct attributed ads, lifetime vs. outstanding commission, and Partnership-ad whitelist status. Every metric is Meta-side (from the attribution ledger) — TikTok organic stats are not mixed in. All cents are in the shop's Meta ad account currency (echoed as currency). Single shop only.
| campaign_id required | string (Campaign Id) [ 1 .. 64 ] characters |
| page | integer (Page) >= 1 Default: 1 1-indexed leaderboard page. |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 100 Leaderboard rows per page. Defaults to the maximum so a caller that ignores paging still receives a full page; read |
{- "totals": {
- "gmv_cents": 0,
- "views": 0,
- "gmv_per_video_cents": 0,
- "ad_spend_cents": 0,
- "commission_owed_cents": 0,
- "ads": 0
}, - "creators": [
- {
- "creator_id": "string",
- "rank": 0,
- "creator_handle": "string",
- "creator_name": "string",
- "avatar_url": "string",
- "instagram_handle": "string",
- "invite_status": "pending",
- "gmv_cents": 0,
- "ad_spend_cents": 0,
- "payout_earned_cents": 0,
- "payout_outstanding_cents": 0,
- "payout_payable_cents": 0,
- "payout_status": "paid",
- "videos": 0,
- "views": 0,
- "whitelist_granted": true,
- "whitelist_status": "no_instagram"
}
], - "creator_total": 0,
- "filtered_total": 0,
- "page": 1,
- "page_size": 100,
- "total_pages": 1,
- "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0
}, - "currency": "string"
}Per-creator rev-share payout balances for the shop (the Meta Ads Payouts tab), plus summary totals: total owed, ready to pay, and paid in the last 30 days. These are Meta rev-share Stripe payouts and are distinct from Creator Community campaign payments at /campaigns/payments/*. All cents are in the shop's Meta ad account currency (echoed as currency). Single shop only.
{- "payouts": [
- {
- "creator_id": "string",
- "creator_handle": "string",
- "creator_name": "string",
- "balance_owed_cents": 0,
- "status": "ready",
- "tax_status": "complete",
- "last_payout_at": "string"
}
], - "total_owed_cents": 0,
- "ready_cents": 0,
- "paid_period_cents": 0,
- "currency": "string"
}Logged Stripe transfers to creators for the shop's Meta Ads rev-share payouts, newest first. Each row is one transfer (amount, method, Stripe transfer id, timestamp). All cents are in the shop's Meta ad account currency. Single shop only.
| limit | integer (Limit) [ 1 .. 500 ] Default: 100 Max rows to return. |
{- "history": [
- {
- "payout_id": "string",
- "id": "string",
- "creator_id": "string",
- "creator_handle": "string",
- "creator_name": "string",
- "amount_cents": 0,
- "method": "string",
- "stripe_transfer_id": "string",
- "created_at": "string"
}
], - "currency": "string"
}Creator videos the shop has uploaded to its Meta ad library, newest first, scoped to the currently connected ad account. Each item carries the source usage-right id, the Meta asset id, the asset name (which ends with RCHR_{tracking_code} behind the shop's own naming template), the tracking_code to build rc_code from, and the rev-share campaign it's assigned to (if any). Single shop only.
{- "data": [
- {
- "usage_right_id": "string",
- "asset_id": "string",
- "asset_name": "string",
- "tracking_code": "string",
- "campaign_id": "string",
- "created_at": "string"
}
], - "items": [
- {
- "usage_right_id": "string",
- "asset_id": "string",
- "asset_name": "string",
- "tracking_code": "string",
- "campaign_id": "string",
- "created_at": "string"
}
], - "currency": "string"
}Whether this shop's Meta integration is live, and what it is wired to: the Facebook Page, the Instagram Business account, the ad account and the Business Manager. This is the cheap call — it reads the stored authorization rather than fanning out to Meta, with a single best-effort Instagram profile fetch layered on top (its failure degrades the ig object, never the response).
connected: false covers both never-connected and revoked/expired-and-not-yet-repaired; a shop in the latter state answers 409 META_REAUTH_REQUIRED on the endpoints that call Meta. Poll this before the network-bound reads. Single shop only.
{- "connected": true,
- "needs_reauth": false,
- "page": {
- "id": "string",
- "name": "string"
}, - "ig": {
- "id": "string",
- "username": "string"
}, - "ad_account": {
- "id": "string",
- "name": "string"
}, - "business": {
- "id": "string",
- "name": "string"
}
}Everything the portal's Meta dashboard header renders, in one call: the connected Instagram account (with follower / media counts and recent engagement), the Page, the ad account (with its currency and account status), the Business Manager, plus counts of recent own and tagged Instagram media.
Where /meta-ads/status is a database read, this fans out across several Graph edges — so it is served stale-while-revalidate from the same Valkey cache the portal uses (fresh under 5 minutes, usable-but-refreshing under 30, rebuilt inline only when cold), and invalidated immediately by any connection change. Every sub-object is open-schema: new fields can appear without a breaking change, and a sub-fetch that fails comes back as an empty object rather than failing the call. Single shop only.
{- "ig": { },
- "page": { },
- "ad_account": { },
- "business": { },
- "recent_own_media_count": 0,
- "recent_tagged_media_count": 0
}Marketing API performance for the shop's connected ad account over a preset window: account-level totals (impressions, clicks, reach, spend, CTR, CPM, conversions) plus a per-ad table carrying the same metrics alongside each ad's creative — thumbnail, headline, body copy, link, call-to-action, and the branded-content partner's Instagram account for Partnership ads.
conversions is the sum of every entry in Meta's actions breakdown, because the insights edge exposes no single conversions field — read it as total attributed actions, not purchases. spend and the derived rates are in currency, the ad account's own currency, which need not match the shop's TikTok currency.
This is paid Meta data straight from the Marketing API and is unrelated to the rev-share attribution figures at /meta-ads/campaigns/{campaign_id}/rollups, which come from Reacher's own ledger — do not add them together. Sub-fetches are best-effort: a failing insights edge yields zeros and an empty ad list rather than an error. Returns 400 when the shop has no ad account linked. Single shop only.
| date_preset | string (Date Preset) Default: "last_30d" Meta Marketing API reporting window. One of: last_14d, last_28d, last_30d, last_3d, last_7d, last_90d, last_month, last_quarter, maximum, this_month, this_quarter, today, yesterday. |
{- "account_id": "string",
- "account_name": "string",
- "currency": "string",
- "date_preset": "last_30d",
- "totals": {
- "impressions": 0,
- "clicks": 0,
- "reach": 0,
- "spend": 0,
- "ctr": 0,
- "cpm": 0,
- "conversions": 0
}, - "ads": [
- {
- "id": "string",
- "name": "string",
- "status": "string",
- "effective_status": "string",
- "created_time": "string",
- "campaign_id": "string",
- "adset_id": "string",
- "creative_id": "string",
- "thumbnail_url": "string",
- "headline": "string",
- "message": "string",
- "link_url": "string",
- "cta_type": "string",
- "partner_ig_user_id": "string",
- "partner_ig_username": "string",
- "video_id": "string",
- "video_url": "string",
- "impressions": 0,
- "clicks": 0,
- "reach": 0,
- "spend": 0,
- "ctr": 0,
- "cpm": 0,
- "conversions": 0
}
]
}Every ad account the connected Meta user can manage, read live from Meta rather than from stored state, each with its name, currency, account status and owning Business Manager. is_current (and the top-level current_ad_account_id) marks the one this shop's ads and /meta-ads/ad-performance figures are read from.
The list is the source of truth for what an account switch could target; switching itself stays in the portal. Requires a live Meta grant — a shop needing reconnection answers 409 META_REAUTH_REQUIRED. Single shop only.
{- "accounts": [
- {
- "account_id": "string",
- "name": "string",
- "currency": "string",
- "account_status": 0,
- "business_id": "string",
- "business_name": "string",
- "is_current": false
}
], - "current_ad_account_id": "string"
}Every Facebook Page the connected Meta user administers, read live from Meta, each resolved to its linked Instagram Business account (ig_user_id / ig_username, with has_instagram as the quick flag). is_current marks the Page this shop is connected through.
The Instagram link matters beyond display: Partnership ads and creator whitelisting both run through the Page's IG account, so a Page with has_instagram: false leaves the connection functionally incomplete even while /meta-ads/status reports connected: true. Single shop only.
{- "pages": [
- {
- "page_id": "string",
- "name": "string",
- "ig_user_id": "string",
- "ig_username": "string",
- "has_instagram": false,
- "is_current": false
}
], - "current_page_id": "string"
}Up to 50 recent Instagram posts tagging the connected Business account — the organic creator content mentioning the brand, which is where whitelisting and Partnership-ad candidates are usually found. Each row carries the caption, media type, permalink, timestamp, the posting handle, and public like_count / comments_count.
Meta restricts this edge to owner-visible fields, so media_url and thumbnail_url are not available for tagged posts (they are owner-only) — render the permalink for click-through. The public engagement counts here are the only engagement signal available for content the brand did not post; per-media /insights will refuse it. owner is best-effort and may be absent when Meta withholds it. Single shop only.
{- "data": [
- {
- "id": "string",
- "caption": "string",
- "media_type": "string",
- "permalink": "string",
- "timestamp": "string",
- "username": "string",
- "owner": { },
- "like_count": 0,
- "comments_count": 0
}
]
}The 24 most recent posts published by the connected Instagram Business account, newest first, with caption, media type, media and thumbnail URLs, permalink, timestamp and public engagement counts.
Unlike tagged media, these are owned posts — so each id here is a valid media_id for /meta-ads/instagram/media/{media_id}/insights, which is the only way to get reach and saves. Media and thumbnail URLs are short-lived signed Meta CDN links; mirror the bytes if you need them to persist. Rows are passed through exactly as Meta returns them. Single shop only.
{- "data": [
- { }
]
}Reach and saves for a single Instagram post, returned as a flat metrics map so new metrics can be added without a breaking change.
Meta serves per-media insights only for media owned by the connected account. Ask for a tagged post — anything from /meta-ads/instagram/tagged-media — and the answer is 422 INSIGHTS_NOT_AVAILABLE; that is Meta's rule, not a Reacher restriction, and the public engagement counts on the tagged-media row are the substitute. Source media_id values from /meta-ads/instagram/own-media. Single shop only.
| media_id required | string (Media Id) [ 1 .. 64 ] characters IG media ID, as returned by /meta-ads/instagram/own-media. |
{- "media_id": "string",
- "metrics": { }
}Resolve an Instagram handle to its public profile — follower count, post count, bio, display name, avatar and IG user ID — using the connected account's Graph access. The handle does not need any relationship with the brand, which makes this the sizing check before reaching out to a creator.
Meta only resolves Business and Creator accounts through this edge: a personal account, a private account, or a handle that does not exist all come back as 404 alike, and no distinction between those cases is available. Pass the handle with or without a leading @.
For repeated or typeahead lookups prefer /meta-ads/instagram/creator-lookup, which returns the same profile from a 5-minute cache and so does not spend Meta API quota per keystroke. Single shop only.
| username required | string (Username) [ 1 .. 64 ] characters Instagram handle to look up; a leading @ is accepted. |
{- "username": "string",
- "name": "",
- "biography": "",
- "profile_picture_url": "",
- "followers_count": 0,
- "media_count": 0,
- "ig_user_id": ""
}Resolve an Instagram handle to a creator profile — IG user ID, handle, name, follower count and avatar — shaped as a suggestions list so the response stays stable as more resolution sources are added. Today exactly one source contributes, Meta's business discovery, so the list holds at most one entry.
Results are cached in Valkey for 5 minutes per shop and query, which is what makes this the endpoint to call on keystroke; /meta-ads/instagram/business-discovery hits Meta every time and returns richer fields.
A handle that cannot be resolved — nonexistent, private, or a personal rather than Business/Creator account — returns an empty suggestions list rather than a 404, so a typeahead never has to treat a miss as an error. verified is always null: Meta's discovery edge does not expose it. Single shop only.
| query required | string (Query) [ 1 .. 64 ] characters Instagram handle or partial name; a leading @ is accepted. |
{- "suggestions": [
- {
- "ig_user_id": "string",
- "username": "string",
- "name": "string",
- "followers_count": 0,
- "verified": true,
- "profile_picture_url": "string",
- "source": "string"
}
]
}Multi-modal creator search backed by the same retrieval pipeline as the portal: profile (natural-language), transcript (spoken-word), video (visual), and lookalike (creator->creator). GET /ai-search/capabilities reports which modes the shop's region supports; transcript/video/lookalike auto-enable per region as their indexes backfill. CSV export (profile results) has a 50,000-row hard cap. Normally requires a single x-shop-id (the shop's region is the search context); an agency key (agency_read scope) may instead search across all its brands at once with x-shop-id: all plus an owned anchor_shop query param, whose region is the search market — the shop-scoped shop_gmv field is then omitted. CSV export stays single-shop.
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. Launched regions use the v3 profile pipeline; other regions fall back to the v2 pipeline (profile is never region-gated). Units: the gmv field is in US dollars; post_rate/engagement_rate are on a 0–100 scale (NOT 0–1). Filter on an exact GMV/post-rate via the flat params (min_gmv, min_post_rate, …) or the equivalent nested filters. Agency keys may run this brand-agnostically across all their brands at once by sending x-shop-id: all with an owned anchor_shop query param, whose region is the search market; in that mode the per-shop shop_gmv field is omitted.
Min Gmv (number) or Min Gmv (null) (Min Gmv) Minimum GMV. | |
Max Gmv (number) or Max Gmv (null) (Max Gmv) Maximum GMV. | |
Min Units Sold (number) or Min Units Sold (null) (Min Units Sold) Minimum units sold. | |
Max Units Sold (number) or Max Units Sold (null) (Max Units Sold) Maximum units sold. | |
Min Post Rate (number) or Min Post Rate (null) (Min Post Rate) Minimum post rate (sample-fulfillment %) on a 0–100 scale, NOT 0–1. Same metric the automations endpoint calls | |
Max Post Rate (number) or Max Post Rate (null) (Max Post Rate) Maximum post rate (sample-fulfillment %) on a 0–100 scale, NOT 0–1. | |
Min Engagement Rate (number) or Min Engagement Rate (null) (Min Engagement Rate) Minimum engagement rate, 0–100. | |
Max Engagement Rate (number) or Max Engagement Rate (null) (Max Engagement Rate) Maximum engagement rate, 0–100. | |
Min Average Views (number) or Min Average Views (null) (Min Average Views) Minimum average views. | |
Max Average Views (number) or Max Average Views (null) (Max Average Views) Maximum average views. | |
Min Followers (integer) or Min Followers (null) (Min Followers) Minimum follower count. | |
Max Followers (integer) or Max Followers (null) (Max Followers) Maximum follower count. | |
Has Email (boolean) or Has Email (null) (Has Email) Only creators with (true) / without (false) an email. | |
Array of Categories (strings) or Categories (null) (Categories) Product categories (canonical names). | |
Array of Gender (strings) or Gender (null) (Gender) Creator gender, e.g. ["female"]. | |
Array of Creator Ethnicity (strings) or Creator Ethnicity (null) (Creator Ethnicity) Creator ethnicity. | |
Array of Age Range (strings) or Age Range (null) (Age Range) Creator age range, e.g. ["25-34"]. | |
Array of Community (strings) or Community (null) (Community) Community hashtags (US/UK/DE/ES only). Fetch valid values from GET /ai-search/hashtags. | |
Array of Creator Level (strings) or Creator Level (null) (Creator Level) Creator GMV level tier, e.g. ["L5","L6","L7"] (L0=$0 through L7=>$1M). Same tiers the filter vocabulary lists as "Creator Level". | |
Array of Languages (strings) or Languages (null) (Languages) Creator languages, e.g. ["english"]. | |
Array of Audience Age (strings) or Audience Age (null) (Audience Age) Audience (follower) age band. | |
Array of Audience Gender (strings) or Audience Gender (null) (Audience Gender) Audience (follower) gender. | |
Array of Content Style (strings) or Content Style (null) (Content Style) Content style, e.g. ["lifestyle"]. | |
Array of Production Quality (strings) or Production Quality (null) (Production Quality) Production quality, e.g. ["raw_ugc"]. | |
Array of Personality Tone (strings) or Personality Tone (null) (Personality Tone) Personality / tone, e.g. ["relatable"]. | |
Array of Face Visibility (strings) or Face Visibility (null) (Face Visibility) Face visibility, e.g. ["sometimes"]. | |
Array of Body Type (strings) or Body Type (null) (Body Type) Body type (~⅓ of creators populated). | |
Array of Hair Type (strings) or Hair Type (null) (Hair Type) Hair type, e.g. ["wavy"]. | |
Query (string) or Query (null) (Query) Natural-language search query. Whitespace-only is treated as no query (browse mode). | |
Filters (object) or Filters (null) (Filters) Optional creator filters (categories, follower/GMV ranges, etc.), same shape as the portal search filters. post_rate is on a 0–100 scale (sample-fulfillment %). Unrecognized filter keys are rejected with a 400 rather than silently ignored. Cannot be combined with the flat filter params (min_gmv, gender, hair_type, etc.) inherited above. | |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
| disable_handle_pin | boolean (Disable Handle Pin) Default: false When true, skip the deterministic exact-@handle pin and return pure semantic results (powers a 'show all matches / full search' flow). When a query is an exact creator handle, the default (false) pins that creator first and sets |
{- "min_gmv": 0,
- "max_gmv": 0,
- "min_units_sold": 0,
- "max_units_sold": 0,
- "min_post_rate": 100,
- "max_post_rate": 100,
- "min_engagement_rate": 100,
- "max_engagement_rate": 100,
- "min_average_views": 0,
- "max_average_views": 0,
- "min_followers": 0,
- "max_followers": 0,
- "has_email": true,
- "categories": [
- "string"
], - "gender": [
- "string"
], - "creator_ethnicity": [
- "string"
], - "age_range": [
- "string"
], - "community": [
- "string"
], - "creator_level": [
- "string"
], - "languages": [
- "string"
], - "audience_age": [
- "string"
], - "audience_gender": [
- "string"
], - "content_style": [
- "string"
], - "production_quality": [
- "string"
], - "personality_tone": [
- "string"
], - "face_visibility": [
- "string"
], - "body_type": [
- "string"
], - "hair_type": [
- "string"
], - "query": "string",
- "filters": { },
- "page": 1,
- "page_size": 20,
- "disable_handle_pin": false
}{- "data": [
- {
- "creator_name": "string",
- "categories": [
- "string"
], - "follower_count": 0,
- "gmv": 0,
- "shop_gmv": 0,
- "post_rate": 0,
- "engagement_rate": 0,
- "average_views": 0,
- "units_sold": 0,
- "bio": "string",
- "top_videos": [
- {
- "video_id": "string",
- "tiktok_url": "string",
- "views": 0,
- "match_type": "string",
- "snippet": "string"
}
], - "match_count": 0,
- "match_score": 0
}
], - "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0,
- "total": 0
}, - "exact_handle_match": false,
- "exact_handle": "string"
}Find creators whose video transcripts match a natural-language phrase. match_sources selects spoken audio and/or on-screen text (defaults to audio). Matched videos carry match_type and a snippet. Requires a single x-shop-id; availability is gated per region.
| query required | string (Query) non-empty Natural-language phrase to match against creator video transcripts (required). |
| match_sources | Array of strings (Match Sources) Items Enum: "audio" "video" Which transcript sources to search: 'audio' (spoken words) and/or 'video' (on-screen text). Defaults to ['audio']. Duplicates are de-duplicated. |
Filters (object) or Filters (null) (Filters) Optional creator filters (categories, follower/GMV ranges, etc.), same shape as the portal search filters. post_rate is on a 0–100 scale (sample-fulfillment %). Unrecognized filter keys are rejected with a 400 rather than silently ignored. | |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
{- "query": "string",
- "match_sources": [
- "audio"
], - "filters": { },
- "page": 1,
- "page_size": 20
}{- "data": [
- {
- "creator_name": "string",
- "categories": [
- "string"
], - "follower_count": 0,
- "gmv": 0,
- "shop_gmv": 0,
- "post_rate": 0,
- "engagement_rate": 0,
- "average_views": 0,
- "units_sold": 0,
- "bio": "string",
- "top_videos": [
- {
- "video_id": "string",
- "tiktok_url": "string",
- "views": 0,
- "match_type": "string",
- "snippet": "string"
}
], - "match_count": 0,
- "match_score": 0
}
], - "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0,
- "total": 0
}, - "exact_handle_match": false,
- "exact_handle": "string"
}Find creators whose video content matches a text description and/or a reference image. Supply query, a base64 image, or both (at least one is required). For binary file uploads use the multipart POST /ai-search/video/upload endpoint instead. Requires a single x-shop-id; availability is gated per region.
Query (string) or Query (null) (Query) Natural-language description of the visual content to match. Optional if 'image' is supplied. | |
Image (string) or Image (null) (Image) Base64-encoded reference image (PNG/JPG/WebP, <=10MB decoded). Optional if 'query' is supplied. A raw 'data: | |
Filters (object) or Filters (null) (Filters) Optional creator filters, same shape as the portal search filters. post_rate is on a 0–100 scale (sample-fulfillment %). Unrecognized filter keys are rejected with a 400 rather than silently ignored. | |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
{- "query": "string",
- "image": "string",
- "filters": { },
- "page": 1,
- "page_size": 20
}{- "data": [
- {
- "creator_name": "string",
- "categories": [
- "string"
], - "follower_count": 0,
- "gmv": 0,
- "shop_gmv": 0,
- "post_rate": 0,
- "engagement_rate": 0,
- "average_views": 0,
- "units_sold": 0,
- "bio": "string",
- "top_videos": [
- {
- "video_id": "string",
- "tiktok_url": "string",
- "views": 0,
- "match_type": "string",
- "snippet": "string"
}
], - "match_count": 0,
- "match_score": 0
}
], - "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0,
- "total": 0
}, - "exact_handle_match": false,
- "exact_handle": "string"
}Find creators similar to a seed set. Seed from any of: seed_creator_ids (handles or ids, up to 50), a seed_crm_group_id, a seed_list_id, or a text query. CRM group / list seeds are resolved to creators server-side, scoped to your shop. Seeds are excluded from the results. Requires a single x-shop-id; availability is gated per region.
Array of Seed Creator Ids (strings) or Seed Creator Ids (null) (Seed Creator Ids) Seed creators (TikTok handles or ids), up to 50. The result set is creators similar to these seeds. | |
Seed Crm Group Id (string) or Seed Crm Group Id (null) (Seed Crm Group Id) Seed from an existing CRM group's members (resolved server-side, scoped to your shop). | |
Seed List Id (integer) or Seed List Id (null) (Seed List Id) Seed from a saved list's members (resolved server-side, scoped to your shop). | |
Query (string) or Query (null) (Query) Text-description lookalike: describe the kind of creator to find. Used when no seed cohort is supplied. | |
Filters (object) or Filters (null) (Filters) Optional creator filters, same shape as the portal search filters. post_rate is on a 0–100 scale (sample-fulfillment %). Unrecognized filter keys are rejected with a 400 rather than silently ignored. | |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
{- "seed_creator_ids": [
- "string"
], - "seed_crm_group_id": "string",
- "seed_list_id": 0,
- "query": "string",
- "filters": { },
- "page": 1,
- "page_size": 20
}{- "data": [
- {
- "creator_name": "string",
- "categories": [
- "string"
], - "follower_count": 0,
- "gmv": 0,
- "shop_gmv": 0,
- "post_rate": 0,
- "engagement_rate": 0,
- "average_views": 0,
- "units_sold": 0,
- "bio": "string",
- "top_videos": [
- {
- "video_id": "string",
- "tiktok_url": "string",
- "views": 0,
- "match_type": "string",
- "snippet": "string"
}
], - "match_count": 0,
- "match_score": 0
}
], - "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0,
- "total": 0
}, - "exact_handle_match": false,
- "exact_handle": "string"
}Report which search modes are available for the caller's shop region. profile is always true; transcript / video / lookalike auto-enable per region as their indexes finish backfilling. Requires a single x-shop-id.
{- "region": "string",
- "modes": {
- "profile": true,
- "transcript": true,
- "video": true,
- "lookalike": true
}
}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. The CSV includes a numeric gmv column in US dollars (the exact GMV, same value /ai-search/creators returns) — parse THIS for GMV, not gmv_segment. To filter server-side by an exact GMV, set min_gmv / max_gmv (or a numeric range under filters.performance.gmv); it is honored the same way the portal search does — a $2,000 floor keeps everyone earning $2,000+ rather than dropping a whole band. NOTE (breaking change vs the old export): gmv_segment is no longer a band like "$1K-$5K" — it is now a rounded USD display figure (e.g. "$120", "$2.1K", "$1.9M") derived from gmv, so anything parsing it as a band must switch to the numeric gmv column. min_post_rate/max_post_rate (aka the automations endpoint's fulfillment_rate) are on a 0–100 scale, not 0–1.
Min Gmv (number) or Min Gmv (null) (Min Gmv) Minimum GMV. | |
Max Gmv (number) or Max Gmv (null) (Max Gmv) Maximum GMV. | |
Min Units Sold (number) or Min Units Sold (null) (Min Units Sold) Minimum units sold. | |
Max Units Sold (number) or Max Units Sold (null) (Max Units Sold) Maximum units sold. | |
Min Post Rate (number) or Min Post Rate (null) (Min Post Rate) Minimum post rate (sample-fulfillment %) on a 0–100 scale, NOT 0–1. Same metric the automations endpoint calls | |
Max Post Rate (number) or Max Post Rate (null) (Max Post Rate) Maximum post rate (sample-fulfillment %) on a 0–100 scale, NOT 0–1. | |
Min Engagement Rate (number) or Min Engagement Rate (null) (Min Engagement Rate) Minimum engagement rate, 0–100. | |
Max Engagement Rate (number) or Max Engagement Rate (null) (Max Engagement Rate) Maximum engagement rate, 0–100. | |
Min Average Views (number) or Min Average Views (null) (Min Average Views) Minimum average views. | |
Max Average Views (number) or Max Average Views (null) (Max Average Views) Maximum average views. | |
Min Followers (integer) or Min Followers (null) (Min Followers) Minimum follower count. | |
Max Followers (integer) or Max Followers (null) (Max Followers) Maximum follower count. | |
Has Email (boolean) or Has Email (null) (Has Email) Only creators with (true) / without (false) an email. | |
Array of Categories (strings) or Categories (null) (Categories) Product categories (canonical names). | |
Array of Gender (strings) or Gender (null) (Gender) Creator gender, e.g. ["female"]. | |
Array of Creator Ethnicity (strings) or Creator Ethnicity (null) (Creator Ethnicity) Creator ethnicity. | |
Array of Age Range (strings) or Age Range (null) (Age Range) Creator age range, e.g. ["25-34"]. | |
Array of Community (strings) or Community (null) (Community) Community hashtags (US/UK/DE/ES only). Fetch valid values from GET /ai-search/hashtags. | |
Array of Creator Level (strings) or Creator Level (null) (Creator Level) Creator GMV level tier, e.g. ["L5","L6","L7"] (L0=$0 through L7=>$1M). Same tiers the filter vocabulary lists as "Creator Level". | |
Array of Languages (strings) or Languages (null) (Languages) Creator languages, e.g. ["english"]. | |
Array of Audience Age (strings) or Audience Age (null) (Audience Age) Audience (follower) age band. | |
Array of Audience Gender (strings) or Audience Gender (null) (Audience Gender) Audience (follower) gender. | |
Array of Content Style (strings) or Content Style (null) (Content Style) Content style, e.g. ["lifestyle"]. | |
Array of Production Quality (strings) or Production Quality (null) (Production Quality) Production quality, e.g. ["raw_ugc"]. | |
Array of Personality Tone (strings) or Personality Tone (null) (Personality Tone) Personality / tone, e.g. ["relatable"]. | |
Array of Face Visibility (strings) or Face Visibility (null) (Face Visibility) Face visibility, e.g. ["sometimes"]. | |
Array of Body Type (strings) or Body Type (null) (Body Type) Body type (~⅓ of creators populated). | |
Array of Hair Type (strings) or Hair Type (null) (Hair Type) Hair type, e.g. ["wavy"]. | |
Query (string) or Query (null) (Query) Natural-language search query. Omit (or pass whitespace-only) to export the full browse universe for your shop's region. | |
Array of Creator Ids (strings) or Creator Ids (null) (Creator Ids) Explicit list of creator IDs to export. Mutually exclusive with query. | |
Filters (object) or Filters (null) (Filters) Optional creator filters (categories, follower/GMV ranges, etc.), same shape as the portal search filters. Applied server-side so the export matches a filtered search. post_rate is on a 0–100 scale (sample-fulfillment %). Unrecognized filter keys are rejected with a 400 rather than silently ignored. Cannot be combined with the flat filter params (min_gmv, gender, hair_type, etc.) inherited above. |
{- "min_gmv": 0,
- "max_gmv": 0,
- "min_units_sold": 0,
- "max_units_sold": 0,
- "min_post_rate": 100,
- "max_post_rate": 100,
- "min_engagement_rate": 100,
- "max_engagement_rate": 100,
- "min_average_views": 0,
- "max_average_views": 0,
- "min_followers": 0,
- "max_followers": 0,
- "has_email": true,
- "categories": [
- "string"
], - "gender": [
- "string"
], - "creator_ethnicity": [
- "string"
], - "age_range": [
- "string"
], - "community": [
- "string"
], - "creator_level": [
- "string"
], - "languages": [
- "string"
], - "audience_age": [
- "string"
], - "audience_gender": [
- "string"
], - "content_style": [
- "string"
], - "production_quality": [
- "string"
], - "personality_tone": [
- "string"
], - "face_visibility": [
- "string"
], - "body_type": [
- "string"
], - "hair_type": [
- "string"
], - "query": "string",
- "creator_ids": [
- "string"
], - "filters": { }
}{- "detail": [
- {
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}The filter vocabulary the /ai-search/* endpoints accept for your shop's region — call this before building a filters payload rather than guessing at keys or enum spellings.
filters maps each categorical field (product categories, follower age and gender, content language, production quality, face visibility, hasEmail, …) to its allowed {label, value} options. sliders gives the numeric ranges — min, max and step for averageViews, engagementRate, gmv, unitsSold, postRate and followers.
The response is region-stripped: a filter whose backing column has effectively no data in your region is omitted entirely, because applying it would return zero creators regardless of the range. postRate is absent for most non-US/UK regions and the community hashtag filter is absent where the hashtag pipeline hasn't tagged enough creators — treat anything missing here as unsupported for your shop.
Region comes from the shop, so this needs a single x-shop-id.
{- "region": "US",
- "filters": {
- "property1": [
- {
- "label": "string",
- "value": "string"
}
], - "property2": [
- {
- "label": "string",
- "value": "string"
}
]
}, - "sliders": {
- "property1": {
- "min": 0,
- "max": 0,
- "step": 0
}, - "property2": {
- "min": 0,
- "max": 0,
- "step": 0
}
}
}Read the shop's creator DM inbox (conversation list + per-conversation message threads, both paginated), read inbox unread/unreplied stats, 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.
The shop's creator DM inbox. Use unreplied_only=true to get conversations awaiting a reply (what an AI agent typically iterates).
| offset | integer (Offset) >= 0 Default: 0 |
| limit | integer (Limit) [ 1 .. 200 ] Default: 50 |
| unread_only | boolean (Unread Only) Default: false |
| unreplied_only | boolean (Unreplied Only) Default: false |
{- "data": [
- {
- "creator_handle": "string",
- "last_message_text": "string",
- "last_message_time": "string",
- "last_message_sender": "string",
- "unread_count": 0
}
], - "offset": 0,
- "limit": 0,
- "has_more": true,
- "total_count": 0,
- "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0
}, - "unread_conversations": 0,
- "unreplied_conversations": 0
}Shop-level counts for the creator DM inbox: total conversations, conversations with unread messages, conversations awaiting a shop reply, and the total unread-message count (the badge number). unread_conversations counts conversations; unread_messages sums the unread messages inside them — they are NOT the same. For per-creator unread detail, page GET /conversations?unread_only=true.
{- "total_conversations": 0,
- "unread_conversations": 0,
- "unreplied_conversations": 0,
- "unread_messages": 0,
- "has_data": true
}| creator_handle required | string (Creator Handle) |
| page | integer (Page) >= 1 Default: 1 |
{- "creator_handle": "string",
- "data": [
- {
- "id": 0,
- "sender": "string",
- "content_type": "string",
- "text": "string",
- "sent_at": "string",
- "sent_via": "string",
- "image": {
- "url": "string",
- "width": 0,
- "height": 0
}, - "product_card": {
- "product_id": "string",
- "product_name": "string",
- "image_url": "string"
}, - "target_collaboration_card": {
- "invitation_group_id": "string"
}, - "free_sample_card": {
- "apply_id": "string"
}, - "crm_text_with_image_card": {
- "title": "string",
- "content": "string",
- "url": "string"
}, - "crm_text_with_products_card": {
- "title": "string",
- "content": "string",
- "products": [
- {
- "product_id": "string",
- "product_name": "string",
- "image_url": "string"
}
]
}
}
], - "page": 0,
- "has_more": true,
- "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0
}, - "page_size": 0,
- "total_count": 0,
- "total_pages": 0,
- "has_previous": false
}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.
| creator_handle required | string (Creator Handle) |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| message required | string (Message) [ 1 .. 4000 ] characters Plain-text reply body to send to the creator. |
{- "message": "string"
}{- "dry_run": true,
- "would_send_to": "string",
- "message": "string"
}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).
| creator_handle required | string (Creator Handle) |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| image_url required | string (Image Url) [ 10 .. 2000 ] characters 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. |
{- "image_url": "stringstri"
}{- "dry_run": true,
- "would_send_to": "string",
- "image_url": "string"
}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).
| creator_handle required | string (Creator Handle) |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| product_id required | string (Product Id) [ 1 .. 64 ] characters A TikTok product_id from THIS shop's catalog. Sending another shop's product_id is rejected with 400. |
{- "product_id": "string"
}{- "dry_run": true,
- "would_send_to": "string",
- "product_id": "string"
}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.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| creator_handle required | string (Creator Handle) [ 1 .. 255 ] characters 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 (string) or Text (null) (Text) Plain-text reply body. Set this OR | |
Image Url (string) or Image Url (null) (Image Url) 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 | |
Product Id (string) or Product Id (null) (Product Id) 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 |
{- "creator_handle": "string",
- "text": "string",
- "image_url": "stringstri",
- "product_id": "string"
}{- "dry_run": true,
- "would_create_for_handle": "string",
- "content_type": "string",
- "text": "string",
- "image_url": "string",
- "product_id": "string"
}Drafts for the given creator on this shop, newest first. Drafts only — does NOT return sent messages (see GET /conversations/{handle}/messages for those).
| creator_handle required | string (Creator Handle) |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 25 |
{- "data": [
- {
- "id": 0,
- "conversation_id": 0,
- "content_type": "string",
- "text_content": "string",
- "payload": { },
- "created_at": "string",
- "expires_at": "string",
- "created_by_api_key_id": 0
}
], - "page": 0,
- "page_size": 0,
- "total": 0,
- "has_more": true,
- "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0
}
}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).
| draft_id required | integer (Draft Id) |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
{- "detail": [
- {
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}Deliver a previously staged draft to the creator and remove it. This is the confirm step of a draft-then-confirm flow: the message body comes from the draft row, never from this request, so what the operator reviewed is what is sent. The draft is claimed (removed) before the send so the same id cannot go out twice, even from two concurrent requests; a draft TikTok refuses (including the daily / weekly messaging caps, returned as 429) is put back for the inbox. Idempotency-Key required. X-Dry-Run: true resolves the draft and checks ownership without contacting TikTok.
| draft_id required | integer (Draft Id) |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
{- "dry_run": true,
- "would_send_draft_id": 0,
- "conversation_id": 0,
- "content_type": "string",
- "text": "string",
- "image_url": "string",
- "product_id": "string"
}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.
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).
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
Status (string) or Status (null) (Status) UI alias: 'active' (= published), 'past' (= ended), 'draft' (= unpublished drafts), or 'archived'. | |
CampaignType (string) or Campaign Type (null) (Campaign Type) Filter: retainer | challenge | leaderboard | live_gmv_challenge. Race, Bingo and Sweepstakes are not exposed through this API yet. | |
Search (string) or Search (null) (Search) | |
Date From (string) or Date From (null) (Date From) YYYY-MM-DD; start_date >= date_from | |
Date To (string) or Date To (null) (Date To) YYYY-MM-DD; end_date <= date_to | |
Sort By (string) or Sort By (null) (Sort By) campaign_name | campaign_type | gmv | start_date | end_date | total_posts | total_views | created_at | |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" |
nullCreate 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.
The 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.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| campaign_name required | string (Campaign Name) <= 255 characters |
| campaign_type required | string (CampaignType) Enum: "retainer" "challenge" "leaderboard" "live_gmv_challenge" "race" "bingo" "sweepstakes" Supported campaign types. |
| creator_source required | string (CreatorSource) Enum: "my_community" "social_army" How creators are sourced for a campaign. |
| start_date required | string (Start Date) |
End Date (string) or End Date (null) (End Date) 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 (string) or Description (null) (Description) REQUIRED for launch. Rich-text (HTML) campaign description shown to creators. Must contain visible text — an empty string or HTML-only value like ' ' is rejected, matching the portal launch form. | |
| status | string (CampaignStatus) Default: "published" Enum: "published" "draft" "deleted" "archived" DB-level campaign status. The frontend filter param 'active'/'past' are UI aliases — 'active' maps to published, 'past' maps to != published. |
required | any (Pricing) |
CampaignProducts (object) or null 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. | |
Array of Creators (objects) or Creators (null) (Creators) Optional list of creators to seed into the campaign. Mirrors the portal's contract exactly (see | |
Window Url (string) or Window Url (null) (Window Url) HTTPS URL — public landing/preview page for this campaign. | |
| is_available_to_all | boolean (Is Available To All) Default: false |
| visibility | string (CampaignVisibility) Default: "invite_link" Enum: "public" "shop_creators" "invite_link" Who can DISCOVER this campaign. |
| requires_approval | boolean (Requires Approval) Default: false |
| exclusive_participation | boolean (Exclusive Participation) Default: false While on, a creator active in another overlapping campaign of this shop cannot be activated here (and vice versa). |
Array of Creator Level (strings) or Creator Level (string) or Creator Level (null) (Creator Level) Creator tier filter (list or CSV string). | |
Support Email (string) or Support Email (null) (Support Email) | |
Short Header Message (string) or Short Header Message (null) (Short Header Message) 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 (object) or Content Guidelines (null) (Content Guidelines) | |
Array of Inspiration Links (strings) or Inspiration Links (null) (Inspiration Links) List of HTTPS URLs (inspiration videos, mood-boards, etc.). | |
| has_tiered_payout | boolean (Has Tiered Payout) Default: false |
Payout Structure (object) or Payout Structure (null) (Payout Structure) | |
Grace Period (integer) or Grace Period (null) (Grace Period) | |
CampaignReminders (object) or null | |
CampaignEmailTemplates (object) or null |
{- "campaign_name": "string",
- "campaign_type": "retainer",
- "creator_source": "my_community",
- "start_date": "string",
- "end_date": "string",
- "description": "string",
- "status": "published",
- "pricing": {
- "campaign_type": "retainer",
- "posts": 1,
- "payment_per_creator": 0,
- "reward_type": "cash",
- "metric_type": "gmv",
- "minimum_gmv_required": 0,
- "minimum_gmv_required_enabled": false,
- "minimum_views_required": 0,
- "minimum_views_required_enabled": false,
- "minimum_videos_required": 0
}, - "products": {
- "all_selected": false,
- "items": [
- {
- "product_id": "string",
- "tap_link": "string",
- "product_name": "string"
}
], - "use_first_product_image": true,
- "custom_image": "string",
- "campaign_image": "string"
}, - "creators": [
- {
- "property1": "string",
- "property2": "string"
}
], - "window_url": "string",
- "is_available_to_all": false,
- "visibility": "public",
- "requires_approval": false,
- "exclusive_participation": false,
- "creator_level": [
- "string"
], - "support_email": "string",
- "short_header_message": "string",
- "content_guidelines": {
- "property1": [
- "string"
], - "property2": [
- "string"
]
}, - "inspiration_links": [
- "string"
], - "has_tiered_payout": false,
- "payout_structure": { },
- "grace_period": 365,
- "reminders": {
- "enabled": false,
- "message_before_3_days": "string",
- "message_before_1_day": "string"
}, - "email_templates": {
- "enabled": true,
- "application": { },
- "acceptance": { },
- "rejection": { }
}
}{- "campaign_id": 0,
- "uuid_id": "string",
- "status": "created",
- "invite_link": "string"
}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.
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
Search (string) or Search (null) (Search) | |
Sort By (string) or Sort By (null) (Sort By) | |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" |
nullAll 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.
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
Search (string) or Search (null) (Search) | |
Sort By (string) or Sort By (null) (Sort By) | |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" |
null| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
Search (string) or Search (null) (Search) | |
Sort By (string) or Sort By (null) (Sort By) | |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" |
{- "overview": {
- "property1": {
- "value": 0,
- "description": "string"
}, - "property2": {
- "value": 0,
- "description": "string"
}
}, - "data": [
- {
- "creator_name": "string",
- "campaign_name": "",
- "paid_amount": "string",
- "reward_type": "cash",
- "paid_at": "string",
- "note": "",
- "tiktok_username": "string",
- "currency": "USD"
}
], - "pagination": {
- "page": 0,
- "page_size": 0,
- "total_count": 0,
- "total_pages": 0
}, - "currency": "USD"
}| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
Sort By (string) or Sort By (null) (Sort By) | |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" |
{- "overview": {
- "property1": {
- "value": 0,
- "description": "string"
}, - "property2": {
- "value": 0,
- "description": "string"
}
}, - "data": [
- {
- "date": "string",
- "amount": "string"
}
], - "pagination": {
- "page": 0,
- "page_size": 0,
- "total_count": 0,
- "total_pages": 0
}, - "currency": "USD"
}Modify campaign fields. campaign_type and creator_source are immutable post-create — pass them and you'll get 400 IMMUTABLE_FIELD.
| campaign_id required | integer (Campaign Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
Campaign Name (string) or Campaign Name (null) (Campaign Name) | |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) | |
Description (string) or Description (null) (Description) | |
CampaignStatus (string) or null | |
Pricing (any) or Pricing (null) (Pricing) | |
CampaignProducts (object) or null | |
Array of Creators (objects) or Creators (null) (Creators) Optional list of creators to seed into the campaign. Mirrors the portal's contract exactly (see | |
Window Url (string) or Window Url (null) (Window Url) | |
Is Available To All (boolean) or Is Available To All (null) (Is Available To All) | |
CampaignVisibility (string) or null Change who can discover this campaign. Omit to leave it as it is. See | |
Requires Approval (boolean) or Requires Approval (null) (Requires Approval) | |
Exclusive Participation (boolean) or Exclusive Participation (null) (Exclusive Participation) | |
Array of Creator Level (strings) or Creator Level (string) or Creator Level (null) (Creator Level) | |
Support Email (string) or Support Email (null) (Support Email) | |
Short Header Message (string) or Short Header Message (null) (Short Header Message) | |
Content Guidelines (object) or Content Guidelines (null) (Content Guidelines) | |
Array of Inspiration Links (strings) or Inspiration Links (null) (Inspiration Links) | |
Has Tiered Payout (boolean) or Has Tiered Payout (null) (Has Tiered Payout) | |
Payout Structure (object) or Payout Structure (null) (Payout Structure) | |
Grace Period (integer) or Grace Period (null) (Grace Period) | |
CampaignReminders (object) or null | |
CampaignEmailTemplates (object) or null |
{- "campaign_name": "string",
- "start_date": "string",
- "end_date": "string",
- "description": "string",
- "status": "published",
- "pricing": {
- "campaign_type": "retainer",
- "posts": 1,
- "payment_per_creator": 0,
- "reward_type": "cash",
- "metric_type": "gmv",
- "minimum_gmv_required": 0,
- "minimum_gmv_required_enabled": false,
- "minimum_views_required": 0,
- "minimum_views_required_enabled": false,
- "minimum_videos_required": 0
}, - "products": {
- "all_selected": false,
- "items": [
- {
- "product_id": "string",
- "tap_link": "string",
- "product_name": "string"
}
], - "use_first_product_image": true,
- "custom_image": "string",
- "campaign_image": "string"
}, - "creators": [
- {
- "property1": "string",
- "property2": "string"
}
], - "window_url": "string",
- "is_available_to_all": true,
- "visibility": "public",
- "requires_approval": true,
- "exclusive_participation": true,
- "creator_level": [
- "string"
], - "support_email": "string",
- "short_header_message": "string",
- "content_guidelines": {
- "property1": [
- "string"
], - "property2": [
- "string"
]
}, - "inspiration_links": [
- "string"
], - "has_tiered_payout": true,
- "payout_structure": { },
- "grace_period": 365,
- "reminders": {
- "enabled": false,
- "message_before_3_days": "string",
- "message_before_1_day": "string"
}, - "email_templates": {
- "enabled": true,
- "application": { },
- "acceptance": { },
- "rejection": { }
}
}{- "campaign_id": 0,
- "status": "updated",
- "participant_sync": {
- "applies_to": "string",
- "fields": [
- "string"
], - "updated": 0,
- "unchanged": 0,
- "skipped_paid": 0
}
}Sets status='deleted'. The row remains in the DB but is hidden from list/detail views. Idempotent.
| campaign_id required | integer (Campaign Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
{- "campaign_id": 0,
- "status": "deleted"
}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.
| campaign_id required | integer (Campaign Id) >= 1 |
nullRanked creator standings for a Leaderboard (contest) campaign: each creator's rank, weighted composite_score with a per-metric score_breakdown, and the raw GMV / views / videos / units stats. When the campaign has a currently-live (or recently-ended) micro-challenge, its windowed blitz board is nested under micro_challenge. Standings are paginated; total_creators is the full participant count. Scores come from the same shared scorer the dashboard and public share page use, so ranks agree across every surface. Single-shop only (the shop sets the response currency).
| campaign_id required | integer (Campaign Id) >= 1 |
| page | integer (Page) >= 1 Default: 1 Page number (1-indexed). |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 Standings per page (max 100). |
{- "campaign_id": 0,
- "campaign_name": "",
- "scoring_mode": "single",
- "metric_type": "gmv",
- "weighted_metrics": [
- { }
], - "start_date": "string",
- "end_date": "string",
- "currency": "USD",
- "currency_code": "USD",
- "currency_symbol": "$",
- "standings": [
- {
- "rank": 0,
- "creator_id": "string",
- "handle": "string",
- "creator_name": "string",
- "avatar_url": "string",
- "composite_score": 0,
- "score_breakdown": [
- {
- "metric_type": "gmv",
- "weight": 0,
- "contribution": 0,
- "raw_value": 0
}
], - "gmv": 0,
- "views": 0,
- "videos_posted": 0,
- "units_sold": 0,
- "avg_views_per_post": 0,
- "live_gmv": 0,
- "live_session_count": 0,
- "combined_gmv": 0,
- "frozen": false
}
], - "micro_challenge": {
- "micro_campaign_id": 0,
- "name": "",
- "window_start_at": "string",
- "window_end_at": "string",
- "status": "live",
- "scoring_mode": "single",
- "metric_type": "gmv",
- "standings": [
- {
- "rank": 0,
- "creator_id": "string",
- "handle": "string",
- "creator_name": "string",
- "avatar_url": "string",
- "composite_score": 0,
- "score_breakdown": [
- {
- "metric_type": "gmv",
- "weight": 0,
- "contribution": 0,
- "raw_value": 0
}
], - "gmv": 0,
- "views": 0,
- "videos_posted": 0,
- "units_sold": 0,
- "avg_views_per_post": 0,
- "live_gmv": 0,
- "live_session_count": 0,
- "combined_gmv": 0,
- "frozen": false
}
]
}, - "total_creators": 0,
- "standings_frozen": false,
- "standings_frozen_at": "string",
- "standings_frozen_reason": "string",
- "standings_period_end": "string",
- "values_windowed": true,
- "as_of": "string",
- "pagination": {
- "page": 0,
- "page_size": 0,
- "total_count": 0,
- "total_pages": 0
}
}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.
| campaign_id required | integer (Campaign Id) >= 1 |
| creator_id required | integer (Creator Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
Message (string) or Message (null) (Message) Optional custom message included in the creator's acceptance email/SMS. If omitted, the campaign's default acceptance template is used. |
{- "message": "string"
}{- "success": true,
- "code": "ok",
- "data": { }
}Sets CampaignCreatorMapping.status='rejected'. Sends a rejection email + SMS to the creator (fire-and-forget) — SKIPPED on dry-run.
| campaign_id required | integer (Campaign Id) >= 1 |
| creator_id required | integer (Creator Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
Message (string) or Message (null) (Message) Optional custom message included in the creator's rejection email/SMS. |
{- "message": "string"
}{- "success": true,
- "code": "ok",
- "data": { }
}Reverses a reject: status goes back to active. No new notification is sent.
| campaign_id required | integer (Campaign Id) >= 1 |
| creator_id required | integer (Creator Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
{- "success": true,
- "code": "ok",
- "data": { }
}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.
Paid/completed creators are blocked (409) — their payout ledger is preserved and never overwritten.
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.
X-Dry-Run: true validates + echoes the intended action without writing or sending the optional SMS.
| creator_id required | integer (Creator Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| campaign_id required | integer (Campaign Id) > 0 Campaign (in the x-shop-id shop) to remove the creator from. |
Message (string) or Message (null) (Message) 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. |
{- "campaign_id": 0,
- "message": "string"
}{- "success": true,
- "code": "ok",
- "data": { }
}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.
Restrictions:
pg_advisory_xact_lock guards it; a paid_at row is never overwritten).Rewards are recomputed server-side from the TARGET campaign. notify (default true) sends the creator an acceptance-style notification — SKIPPED on X-Dry-Run.
| creator_id required | integer (Creator Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| target_campaign_id required | integer (Target Campaign Id) > 0 Campaign the creator is being added to (lives in target_shop_id). |
| target_shop_id required | integer (Target Shop Id) > 0 Shop that owns the target campaign. Must belong to the same customer as your API key (else 404). |
| notify | boolean (Notify) Default: true Send the creator an acceptance-style notification on add. SKIPPED on X-Dry-Run. |
{- "target_campaign_id": 0,
- "target_shop_id": 0,
- "notify": true
}{- "success": true,
- "code": "ok",
- "data": { }
}| video_id required | string (Video Id) [ 1 .. 64 ] characters |
{- "video_id": "string"
}{- "status": "success",
- "data": [
- { }
], - "count": 0
}Used by the settlement screen to enumerate the videos a creator posted under a specific campaign.
| campaign_creator_mapping_id required | integer (Campaign Creator Mapping Id) > 0 |
{- "campaign_creator_mapping_id": 0
}{- "status": "success",
- "data": [
- { }
], - "count": 0
}Soft-deletes a CampaignCreatorMapping's content tracker so it no longer surfaces in campaign content lists.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| campaign_creator_mapping_id required | integer (Campaign Creator Mapping Id) > 0 |
{- "campaign_creator_mapping_id": 0
}{- "status": "string",
- "message": "string"
}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.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| creator_id required | integer (Creator Id) > 0 |
| campaign_creator_mapping_id required | integer (Campaign Creator Mapping Id) > 0 |
| campaign_id required | integer (Campaign Id) > 0 |
| additional_posts required | integer (Additional Posts) [ 1 .. 100 ] |
required | New Total Payout (number) or New Total Payout (string) (New Total Payout) New total payout — number for cash campaigns, string label (e.g. 'free product') for custom-reward campaigns. |
| minimum_gmv_target required | number (Minimum Gmv Target) > 0 |
Tiktok Username (string) or Tiktok Username (null) (Tiktok Username) | |
CreatorStatus (string) or null |
{- "creator_id": 0,
- "campaign_creator_mapping_id": 0,
- "campaign_id": 0,
- "additional_posts": 1,
- "new_total_payout": 0,
- "minimum_gmv_target": 0,
- "tiktok_username": "string",
- "creator_status": "pending"
}{- "status": "success"
}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.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| amount required | number (Amount) ( 0 .. 100000 ] Deposit amount |
| currency | string (Currency) = 3 characters Default: "USD" ISO 4217 currency. The wallet's persisted currency overrides this for cross-currency safety. |
| success_url required | string (Success Url) <= 2048 characters 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 required | string (Cancel Url) <= 2048 characters |
{- "amount": 100000,
- "currency": "USD",
- "success_url": "string",
- "cancel_url": "string"
}{- "session_id": "string",
- "redirect_url": "string"
}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.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| creator_id required | integer (Creator Id) > 0 |
| campaign_id required | integer (Campaign Id) > 0 |
| amount required | number (Amount) ( 0 .. 100000 ] Settlement amount in the campaign's currency. Capped at $100,000 per call as a sanity guard. |
| currency required | string (Currency) = 3 characters 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 |
| reward_type required | string (Reward Type) ^(cash|custom)$ 'cash' triggers a Stripe transfer + wallet debit; 'custom' records the settlement only (no money moves). |
| campaign_creator_mapping_id required | integer (Campaign Creator Mapping Id) > 0 REQUIRED. The campaign-creator mapping id that the settlement is being recorded against. The portal surfaces this id on every creator row in |
Metadata (object) or Metadata (null) (Metadata) Free-form metadata stored alongside the settlement (e.g. agent annotation, internal reference). |
{- "creator_id": 0,
- "campaign_id": 0,
- "amount": 100000,
- "currency": "str",
- "reward_type": "string",
- "campaign_creator_mapping_id": 0,
- "metadata": { }
}{- "success": true,
- "transfer_id": "string",
- "amount": 0,
- "currency": "string",
- "paid_at": "string",
- "wallet_balance": 0
}Sets status='archived' — preserves the row in list/detail views but marks it as no longer active.
| campaign_id required | integer (Campaign Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
{- "campaign_id": 0,
- "status": "archived"
}For creators linked to your Creator Community campaigns, returns each creator's TikTok handle alongside their linked Discord handle — so you can match Discord server members to the TikTok Shop creators you work with.
One row per (campaign, creator): a creator in several of your campaigns appears once per campaign, with campaign_id / campaign_name for context. Discord is creator-provided and optional; by default only creators who have linked a Discord account are returned. Set with_discord_only=false to include creators without one (discord_handle and discord_user_id will be null).
By default every currently-linked creator is returned except those rejected / removed / withdrawn from a campaign; pass active_only=true to restrict to creators accepted into a campaign (active or settled). Single shop only.
| page | integer (Page) >= 1 Default: 1 Page number (1-indexed). |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 50 Rows per page (max 100). |
Campaign Id (integer) or Campaign Id (null) (Campaign Id) Restrict to a single campaign you own. Must belong to the shop in x-shop-id, otherwise the result is simply empty. | |
| with_discord_only | boolean (With Discord Only) Default: true Return only creators who have linked a Discord account. Set false to include creators without one (their Discord fields are null). |
| active_only | boolean (Active Only) Default: false Restrict to creators accepted into a campaign (active or settled). Default returns all linked creators except rejected / removed / withdrawn. |
{- "data": [
- {
- "tiktok_handle": "string",
- "discord_handle": "string",
- "discord_user_id": "string",
- "campaign_id": 0,
- "campaign_name": "string"
}
], - "pagination": null
}Market intelligence across the TikTok Shop ecosystem — sellers, products, creators, videos, benchmarks. These endpoints require Social Intelligence access to be enabled for your account — contact Reacher to request access. The reads normally require a single x-shop-id (to pick the region and mark your own brand); an agency key (agency_read scope) sending x-shop-id: all may instead name an owned anchor_shop query param, which is used as the request's single shop — you get the same ecosystem-wide data, anchored to that owned brand's region and self-markers.
Paginated list of sellers across the TikTok Shop ecosystem. Filter by category, search by name, sort by gmv28d, gmvAllTime, etc. Includes a yourBrand field showing your shop's rank in the same list.
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
Search (string) or Search (null) (Search) | |
Category (string) or Category (null) (Category) | |
Subcategory (string) or Subcategory (null) (Subcategory) | |
Sort By (string) or Sort By (null) (Sort By) e.g. gmv28d, gmvAllTime, creators, videos, rating | |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" |
nullCreators who have driven GMV for this seller. Filter by GMV, follower count, shop posts, post rate, demographic categories.
| seller_id required | string (Seller Id) <= 64 characters |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
Search (string) or Search (null) (Search) | |
Sort By (string) or Sort By (null) (Sort By) gmv28d, followers, shopPosts, postRate, engagement | |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" |
Min Gmv (number) or Min Gmv (null) (Min Gmv) | |
Max Gmv (number) or Max Gmv (null) (Max Gmv) | |
Min Gmv 28D (number) or Min Gmv 28D (null) (Min Gmv 28D) | |
Max Gmv 28D (number) or Max Gmv 28D (null) (Max Gmv 28D) | |
Min Followers (integer) or Min Followers (null) (Min Followers) | |
Max Followers (integer) or Max Followers (null) (Max Followers) | |
Min Units Sold (number) or Min Units Sold (null) (Min Units Sold) | |
Max Units Sold (number) or Max Units Sold (null) (Max Units Sold) | |
Min Post Rate (number) or Min Post Rate (null) (Min Post Rate) | |
Max Post Rate (number) or Max Post Rate (null) (Max Post Rate) | |
Min Shop Posts (integer) or Min Shop Posts (null) (Min Shop Posts) | |
Max Shop Posts (integer) or Max Shop Posts (null) (Max Shop Posts) | |
Categories (string) or Categories (null) (Categories) Comma-separated category names |
nullAll products in this seller's catalog with sales metrics.
| seller_id required | string (Seller Id) <= 64 characters |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
Search (string) or Search (null) (Search) | |
Sort By (string) or Sort By (null) (Sort By) gmv28d, unitsSold, rating, creators, videos | |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" |
nullVideos featuring products from this seller. Includes AI-analyzed content tags (hookType, sellingPoints, contentAnalysis) for top performers.
| seller_id required | string (Seller Id) <= 64 characters |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
Search (string) or Search (null) (Search) | |
Sort By (string) or Sort By (null) (Sort By) gmv, views, likes, engagement, date | |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" |
Time Range (string) or Time Range (null) (Time Range) Predefined window. Use start_date/end_date for custom ranges. | |
Min Gmv (number) or Min Gmv (null) (Min Gmv) | |
Max Gmv (number) or Max Gmv (null) (Max Gmv) | |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) | |
Min Engagement (number) or Min Engagement (null) (Min Engagement) | |
Max Engagement (number) or Max Engagement (null) (Max Engagement) | |
Min Views (integer) or Min Views (null) (Min Views) | |
Max Views (integer) or Max Views (null) (Max Views) |
nullPaginated list of products across the TikTok Shop ecosystem. Filter by category, search by name, sort by gmv28d, unitsSold, etc.
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
Search (string) or Search (null) (Search) | |
Category (string) or Category (null) (Category) | |
Subcategory (string) or Subcategory (null) (Subcategory) | |
Sort By (string) or Sort By (null) (Sort By) gmv28d, unitsSold, rating, creators, videos | |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" |
nullExport the product list in one call — same filters as GET /products, but without pagination. Returns JSON rows by default; pass format=csv for a downloadable file.
Capped at 1000 rows for most customers; truncated: true in the JSON response means the cap clipped the result. Consumes one of the shop's daily exports, a quota shared with portal exports.
| format | string (Format) ^(json|csv)$ Default: "json" Output format. |
Search (string) or Search (null) (Search) | |
Category (string) or Category (null) (Category) | |
Subcategory (string) or Subcategory (null) (Subcategory) | |
Sort By (string) or Sort By (null) (Sort By) gmv28d, unitsSold, rating, creators, videos | |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" |
{- "data": [
- null
], - "row_count": 0,
- "row_limit": 0,
- "truncated": true,
- "columns": [
- "string"
]
}Export every creator driving GMV for a product — same filters as GET /products/{product_id}/creators, without pagination. JSON rows by default; format=csv for a file. Consumes one daily export.
| product_id required | string (Product Id) <= 64 characters |
| format | string (Format) ^(json|csv)$ Default: "json" Output format. |
Search (string) or Search (null) (Search) | |
Sort By (string) or Sort By (null) (Sort By) gmv28d, followers, shopPosts, postRate | |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" |
Seller Id (string) or Seller Id (null) (Seller Id) | |
Min Gmv (number) or Min Gmv (null) (Min Gmv) | |
Max Gmv (number) or Max Gmv (null) (Max Gmv) | |
Min Gmv 28D (number) or Min Gmv 28D (null) (Min Gmv 28D) | |
Max Gmv 28D (number) or Max Gmv 28D (null) (Max Gmv 28D) | |
Min Followers (integer) or Min Followers (null) (Min Followers) | |
Max Followers (integer) or Max Followers (null) (Max Followers) | |
Min Units Sold (number) or Min Units Sold (null) (Min Units Sold) | |
Max Units Sold (number) or Max Units Sold (null) (Max Units Sold) | |
Min Post Rate (number) or Min Post Rate (null) (Min Post Rate) | |
Max Post Rate (number) or Max Post Rate (null) (Max Post Rate) | |
Min Shop Posts (integer) or Min Shop Posts (null) (Min Shop Posts) | |
Max Shop Posts (integer) or Max Shop Posts (null) (Max Shop Posts) | |
Categories (string) or Categories (null) (Categories) Comma-separated category names |
{- "data": [
- null
], - "row_count": 0,
- "row_limit": 0,
- "truncated": true,
- "columns": [
- "string"
]
}Export every creator working with a seller — same filters as GET /sellers/{seller_id}/creators, without pagination. JSON rows by default; format=csv for a file. Consumes one daily export.
| seller_id required | string (Seller Id) <= 64 characters |
| format | string (Format) ^(json|csv)$ Default: "json" Output format. |
Search (string) or Search (null) (Search) | |
Sort By (string) or Sort By (null) (Sort By) gmv28d, followers, shopPosts, postRate | |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" |
Min Gmv (number) or Min Gmv (null) (Min Gmv) | |
Max Gmv (number) or Max Gmv (null) (Max Gmv) | |
Min Gmv 28D (number) or Min Gmv 28D (null) (Min Gmv 28D) | |
Max Gmv 28D (number) or Max Gmv 28D (null) (Max Gmv 28D) | |
Min Followers (integer) or Min Followers (null) (Min Followers) | |
Max Followers (integer) or Max Followers (null) (Max Followers) | |
Min Units Sold (number) or Min Units Sold (null) (Min Units Sold) | |
Max Units Sold (number) or Max Units Sold (null) (Max Units Sold) | |
Min Post Rate (number) or Min Post Rate (null) (Min Post Rate) | |
Max Post Rate (number) or Max Post Rate (null) (Max Post Rate) | |
Min Shop Posts (integer) or Min Shop Posts (null) (Min Shop Posts) | |
Max Shop Posts (integer) or Max Shop Posts (null) (Max Shop Posts) | |
Categories (string) or Categories (null) (Categories) Comma-separated category names |
{- "data": [
- null
], - "row_count": 0,
- "row_limit": 0,
- "truncated": true,
- "columns": [
- "string"
]
}Creators who have driven GMV for this product. Pass seller_id to scope to a specific seller's affiliate creators.
| product_id required | string (Product Id) <= 64 characters |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
Search (string) or Search (null) (Search) | |
Sort By (string) or Sort By (null) (Sort By) gmv28d, followers, shopPosts, postRate | |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" |
Seller Id (string) or Seller Id (null) (Seller Id) | |
Min Gmv (number) or Min Gmv (null) (Min Gmv) | |
Max Gmv (number) or Max Gmv (null) (Max Gmv) | |
Min Gmv 28D (number) or Min Gmv 28D (null) (Min Gmv 28D) | |
Max Gmv 28D (number) or Max Gmv 28D (null) (Max Gmv 28D) | |
Min Followers (integer) or Min Followers (null) (Min Followers) | |
Max Followers (integer) or Max Followers (null) (Max Followers) | |
Min Units Sold (number) or Min Units Sold (null) (Min Units Sold) | |
Max Units Sold (number) or Max Units Sold (null) (Max Units Sold) | |
Min Post Rate (number) or Min Post Rate (null) (Min Post Rate) | |
Max Post Rate (number) or Max Post Rate (null) (Max Post Rate) | |
Min Shop Posts (integer) or Min Shop Posts (null) (Min Shop Posts) | |
Max Shop Posts (integer) or Max Shop Posts (null) (Max Shop Posts) | |
Categories (string) or Categories (null) (Categories) Comma-separated category names |
nullVideos featuring this product. Includes AI-analyzed content tags (hookType, sellingPoints, contentAnalysis) for top performers.
| product_id required | string (Product Id) <= 64 characters |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
Search (string) or Search (null) (Search) | |
Sort By (string) or Sort By (null) (Sort By) gmv, views, likes, engagement, date | |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" |
Time Range (string) or Time Range (null) (Time Range) | |
Min Gmv (number) or Min Gmv (null) (Min Gmv) | |
Max Gmv (number) or Max Gmv (null) (Max Gmv) | |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) | |
Min Engagement (number) or Min Engagement (null) (Min Engagement) | |
Max Engagement (number) or Max Engagement (null) (Max Engagement) | |
Min Views (integer) or Min Views (null) (Min Views) | |
Max Views (integer) or Max Views (null) (Max Views) |
nullTop-performing videos across the entire TikTok Shop ecosystem. Filter by category, time range, GMV, views, engagement. Includes AI-analyzed content tags for top performers.
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
Search (string) or Search (null) (Search) | |
Category (string) or Category (null) (Category) | |
Subcategory (string) or Subcategory (null) (Subcategory) | |
Sort By (string) or Sort By (null) (Sort By) gmv, views, likes, engagement, date | |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" |
Time Range (string) or Time Range (null) (Time Range) | |
Min Gmv (number) or Min Gmv (null) (Min Gmv) | |
Max Gmv (number) or Max Gmv (null) (Max Gmv) | |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) | |
Min Engagement (number) or Min Engagement (null) (Min Engagement) | |
Max Engagement (number) or Max Engagement (null) (Max Engagement) | |
Min Views (integer) or Min Views (null) (Min Views) | |
Max Views (integer) or Max Views (null) (Max Views) |
nullThe spoken-audio transcript of a Social Intelligence (competitor) video — one of the videos surfaced by the seller / product / trending video endpoints. Pass a video_id from one of those lists. Returns available: false when no transcript is held yet (e.g. no_speech, still processing, or the video isn't in our SI corpus). For your OWN shop's videos use the video creative-analysis endpoint (/videos/{video_id}/analysis) instead — that one carries the transcript plus the full creative breakdown.
| video_id required | string (Video Id) <= 255 characters |
{- "video_id": "string",
- "seller_id": "string",
- "available": true,
- "transcript": "string",
- "transcript_language": "string",
- "transcript_status": "string"
}Your shop's seven headline metrics — GMV, videos posted, video views, active creators, GMV per video, GMV per sample, average videos per creator — each with its current value, period-over-period percent_change, a daily shop_timeseries, and the matching ecosystem benchmark_avg / benchmark_p90 and benchmark_timeseries.
percentile_all ranks you against every tracked shop; percentile_segment ranks you inside your own GMV segment (gmv_segment), which is the fairer comparison. Precomputed daily — computed_date is the snapshot the numbers come from.
nullYour shop's outreach funnel — DMs sent, TC invites sent, samples requested, samples approved, videos posted, videos converted — with each stage's value, the Reacher-wide benchmark_avg, and the stage-to-stage conversion_rate against benchmark_conversion_rate.
Stages converting below the benchmark are flagged status: action_required and carry a recommendation. biggest_dropoff_stage names the weakest link. Also returns content_unfulfilled — creators who took a sample and never posted.
nullThe raw ecosystem benchmark distribution for one GMV segment, independent of your own shop — average and p90 for each headline metric plus a daily benchmark_timeseries.
Pass any segment, not just your own: all (every shop), segment_1 ($0–5K), segment_2 ($5K–50K), segment_3 ($50K–100K), segment_4 ($100K+). Use this to size the gap to the tier above you; use /my-benchmarks for where you sit today.
| segment | string (Segment) Default: "all" GMV segment key. One of: all, segment_1, segment_2, segment_3, segment_4. |
nullEvery creator tracked in your region's TikTok Shop ecosystem — not just your affiliates. Each row carries the profile (handle, name, avatar, follower count, content categories) and windowed performance (GMV, average views, shop posts, engagement rate) over a 1-, 7- or 28-day window.
isMyAffiliate marks creators already in your CRM; set hide_my_affiliates=true to return only new prospects. Results are capped at 1000 rows — capped: true with totalMatching above that means you should narrow with search / category before paging.
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
Search (string) or Search (null) (Search) | |
Category (string) or Category (null) (Category) | |
Subcategory (string) or Subcategory (null) (Subcategory) | |
Sort By (string) or Sort By (null) (Sort By) gmv, avgViews, shopPosts, engagement, followers | |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" |
| window_days | integer (Window Days) Default: 28 Metric window in days. Only 1, 7, 28 are backed by data; anything else falls back to 28. The window is echoed back as |
| hide_my_affiliates | boolean (Hide My Affiliates) Default: false Exclude creators already affiliated with your shop. |
nullWhen the community creator dataset was last synced for your shop's region. Use it to decide whether a re-poll is worthwhile — the directory refreshes on a schedule, not continuously. lastUpdated is null when no sync has landed for the region yet.
nullEcosystem-wide profile for one creator by TikTok handle: name, avatar, TikTok URL, region, content categories, and windowed performance (GMV, average views, shop posts, engagement rate, followers).
This is the community view — what the creator does across all brands, which is the only view that exists for creators you have never worked with. For their history with your shop, use the CRM endpoints.
| handle required | string (Handle) <= 100 characters |
| window_days | integer (Window Days) Default: 28 Metric window in days. Only 1, 7, 28 are backed by data; anything else falls back to 28. The window is echoed back as |
nullVideos this creator posted for TikTok Shop brands, newest or top-performing first. Each row is the standard video card — views, likes, comments, shares, engagement, GMV, product, thumbnail, TikTok URL — plus brandName, the brand the video was made for.
Pass seller_id to restrict to one brand's videos, or days to window relative to the creator's most recent post. Capped at 500 rows (capped flags truncation).
| handle required | string (Handle) <= 100 characters |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
Sort By (string) or Sort By (null) (Sort By) gmv, views, likes, engagement, date | |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" |
Days (integer) or Days (null) (Days) Lookback anchored on the creator's latest video. Omit for all time. | |
Seller Id (string) or Seller Id (null) (Seller Id) Restrict to one brand's videos. |
nullThe creator's per-brand breakdown — every seller they have posted for, with videos, gmv, unitsSold, avgViews and latestVideoAt for each. The competitive-overlap view: who else is already working with this creator, and how well it is going.
Pass days to window relative to the creator's most recent post, or search to filter by brand name. total is the full brand count, not just the current page.
| handle required | string (Handle) <= 100 characters |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 50 ] Default: 10 |
Days (integer) or Days (null) (Days) Lookback anchored on the creator's latest video. Omit for all time. | |
Search (string) or Search (null) (Search) Filter by brand name. |
nullExport the trending-video list in one call — same filters as GET /social-intelligence/videos/trending, but without pagination. Pass video_ids to export a specific selection instead.
Returns JSON rows by default; pass format: "csv" for a downloadable file. Capped at 1000 rows for most customers; truncated: true in the JSON response means the cap clipped the result. Read-only, but it consumes one of the shop's daily exports — a quota shared with portal exports.
Array of Video Ids (strings) or Video Ids (null) (Video Ids) Export these specific videos. Filters below are then ignored. | |
Search (string) or Search (null) (Search) | |
Category (string) or Category (null) (Category) | |
Subcategory (string) or Subcategory (null) (Subcategory) | |
Sort By (string) or Sort By (null) (Sort By) gmv, views, likes, engagement, date | |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" |
Time Range (string) or Time Range (null) (Time Range) | |
Min Gmv (number) or Min Gmv (null) (Min Gmv) | |
Max Gmv (number) or Max Gmv (null) (Max Gmv) | |
Start Date (string) or Start Date (null) (Start Date) | |
End Date (string) or End Date (null) (End Date) | |
Min Engagement (number) or Min Engagement (null) (Min Engagement) | |
Max Engagement (number) or Max Engagement (null) (Max Engagement) | |
Min Views (integer) or Min Views (null) (Min Views) | |
Max Views (integer) or Max Views (null) (Max Views) | |
| format | string (Format) ^(json|csv)$ Default: "json"
|
{- "video_ids": [
- "string"
], - "search": "string",
- "category": "string",
- "subcategory": "string",
- "sort_by": "string",
- "sort_order": "desc",
- "time_range": "string",
- "min_gmv": 0,
- "max_gmv": 0,
- "start_date": "string",
- "end_date": "string",
- "min_engagement": 0,
- "max_engagement": 0,
- "min_views": 0,
- "max_views": 0,
- "format": "json"
}{- "data": [
- null
], - "row_count": 0,
- "row_limit": 0,
- "truncated": true,
- "columns": [
- "string"
]
}The shop's Social Intelligence watchlist for one entity type, most recently added first. Returns bare IDs — feed a brand ID into the seller endpoints or a product ID into the product endpoints to resolve it.
Favorites are per shop, not per user: everyone on the account sees the same watchlist. Read-only — no read_write scope needed. Single-shop only.
| entity_type required | string (Entity Type) ^(brand|product)$
|
{- "entity_type": "string",
- "entity_ids": [
- "string"
]
}Favorite one competitor brand or one product so it surfaces in the shop's Social Intelligence watchlist.
Idempotent: favoriting something already on the list is a no-op that still returns 200, so a client does not have to read the list first. The ID is not validated against the SI catalog — an unknown ID is stored and simply resolves to nothing on read.
Favorites are per shop, so this is visible to everyone on the account. Requires read_write scope. Single-shop only.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) |
| entity_type required | string (Entity Type) ^(brand|product)$
|
| entity_id required | string (Entity Id) [ 1 .. 255 ] characters Seller ID for |
{- "entity_type": "string",
- "entity_id": "string"
}{- "ok": true,
- "entity_type": "string",
- "entity_id": "string"
}Un-favorite one brand or product.
Returns 404 when the ID is not on this shop's watchlist — including when it is on another shop's — so a caller can neither delete nor probe a watchlist it does not own. Requires read_write scope. Single-shop only.
| entity_type required | string (Entity Type) ^(brand|product)$
|
| entity_id required | string (Entity Id) [ 1 .. 255 ] characters Seller ID or product ID. |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) |
{- "ok": true,
- "entity_type": "string",
- "entity_id": "string"
}Post to your connected Discord server — list channels, post a message now, schedule one or a recurring (e.g. weekly) series, and read back scheduled / sent messages with their engagement. Requires a connected Discord integration; posting needs a read_write key. Supports text, a bold title, mentions, and base64 image / file attachments.
Every text / announcement channel in the shop's connected Discord server, with can_post telling you where the Reacher bot can actually post. Use a channel's id as channel_id when posting.
404 if the shop has not connected Discord. Single shop only.
{- "data": {
- "discord_guild_id": "string",
- "channels": [
- {
- "id": "string",
- "name": "string",
- "type": 0,
- "position": 0,
- "parent_id": "string",
- "parent_name": "string",
- "can_post": true
}
]
}
}Post a message to a channel immediately, schedule it for later, or start a recurring series (e.g. a weekly announcement).
scheduled_for — the response is the final sent / failed record.scheduled_for.scheduled_for (first fire) and recurrence (e.g. {"frequency": "weekly", "end_type": "never"}).Idempotency-Key is required so a retry can't double-post. X-Dry-Run: true validates without posting or scheduling. Attach up to 10 images / files via images (each base64-encoded, ≤ 8MB) — e.g. a video-breakdown image.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| channel_id required | string (Channel Id) [ 1 .. 50 ] characters Target Discord channel id. |
| content required | string (Content) [ 1 .. 2000 ] characters Message body (≤ 2000 chars). |
Title (string) or Title (null) (Title) Optional bold header rendered above the body. | |
object (MentionTarget) Who to ping: | |
Scheduled For (string) or Scheduled For (null) (Scheduled For) When to post (ISO 8601). Omit to post immediately. | |
Timezone (string) or Timezone (null) (Timezone) IANA tz for the schedule (defaults to the shop's). | |
Recurrence (object) or null Repeat the message. | |
Campaign Id (integer) or Campaign Id (null) (Campaign Id) Optional campaign to associate the post with. | |
Sender User Id (string) or Sender User Id (null) (Sender User Id) Discord user id of a member of the linked server to post AS — the message shows their server nickname and avatar instead of the brand's (Discord still marks it APP). Omit to post as the brand. Bots and non-members are rejected with 400. | |
Array of Images (objects) or Images (null) (Images) Up to 10 images / files to attach (e.g. a video-breakdown image), each base64-encoded and ≤ 8MB decoded. |
{- "channel_id": "string",
- "content": "string",
- "title": "string",
- "mention": {
- "type": "none",
- "role_id": "string"
}, - "scheduled_for": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "recurrence": {
- "frequency": "daily",
- "end_type": "never",
- "until": "2019-08-24",
- "occurrences": 0
}, - "campaign_id": 0,
- "sender_user_id": "string",
- "images": [
- {
- "filename": "string",
- "content_type": "string",
- "data_base64": "string"
}
]
}{- "data": {
- "id": 0,
- "channel_id": "string",
- "channel_name": "string",
- "title": "string",
- "content": "string",
- "mention": {
- "type": "none",
- "role_id": "string"
}, - "campaign_id": 0,
- "attachments": [
- {
- "filename": "string",
- "content_type": "string",
- "size_bytes": 0
}
], - "sender": {
- "type": "brand",
- "user_id": "string",
- "display_name": "string",
- "avatar_url": "string"
}, - "status": "string",
- "scheduled_for": "string",
- "timezone": "string",
- "recurrence": {
- "frequency": "daily",
- "end_type": "never",
- "until": "2019-08-24",
- "occurrences": 0
}, - "paused": false,
- "series_id": 0,
- "occurrence_num": 0,
- "occurrences_sent": 0,
- "discord_message_id": "string",
- "message_url": "string",
- "sent_at": "string",
- "failure_reason": "string",
- "failure_code": "string",
- "audience_count": 0,
- "reaction_count": 0,
- "reply_count": 0,
- "link_click_count": 0,
- "upcoming_occurrences": [
- "2019-08-24T14:15:22Z"
], - "created_at": "string",
- "updated_at": "string"
}, - "dry_run": false
}Discord messages this shop has scheduled or posted through Reacher, with their engagement (reaction_count, reply_count, audience_count) once delivered. counts totals each tab. Single shop only.
| tab | string (Tab) Default: "scheduled" Enum: "scheduled" "sent" "failed" Which set to return. |
| page | integer (Page) >= 1 Default: 1 Page number (1-indexed). |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 25 Rows per page (max 100). |
{- "data": [
- {
- "id": 0,
- "channel_id": "string",
- "channel_name": "string",
- "title": "string",
- "content": "string",
- "mention": {
- "type": "none",
- "role_id": "string"
}, - "campaign_id": 0,
- "attachments": [
- {
- "filename": "string",
- "content_type": "string",
- "size_bytes": 0
}
], - "sender": {
- "type": "brand",
- "user_id": "string",
- "display_name": "string",
- "avatar_url": "string"
}, - "status": "string",
- "scheduled_for": "string",
- "timezone": "string",
- "recurrence": {
- "frequency": "daily",
- "end_type": "never",
- "until": "2019-08-24",
- "occurrences": 0
}, - "paused": false,
- "series_id": 0,
- "occurrence_num": 0,
- "occurrences_sent": 0,
- "discord_message_id": "string",
- "message_url": "string",
- "sent_at": "string",
- "failure_reason": "string",
- "failure_code": "string",
- "audience_count": 0,
- "reaction_count": 0,
- "reply_count": 0,
- "link_click_count": 0,
- "upcoming_occurrences": [
- "2019-08-24T14:15:22Z"
], - "created_at": "string",
- "updated_at": "string"
}
], - "counts": {
- "scheduled": 0,
- "sent": 0,
- "failed": 0
}, - "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0
}
}One scheduled or delivered message. For a recurring series' parent, upcoming_occurrences lists the next fire times. A message id that isn't this shop's reads as 404. Single shop only.
| message_id required | integer (Message Id) >= 1 Message id from a list / create response. |
{- "data": {
- "id": 0,
- "channel_id": "string",
- "channel_name": "string",
- "title": "string",
- "content": "string",
- "mention": {
- "type": "none",
- "role_id": "string"
}, - "campaign_id": 0,
- "attachments": [
- {
- "filename": "string",
- "content_type": "string",
- "size_bytes": 0
}
], - "sender": {
- "type": "brand",
- "user_id": "string",
- "display_name": "string",
- "avatar_url": "string"
}, - "status": "string",
- "scheduled_for": "string",
- "timezone": "string",
- "recurrence": {
- "frequency": "daily",
- "end_type": "never",
- "until": "2019-08-24",
- "occurrences": 0
}, - "paused": false,
- "series_id": 0,
- "occurrence_num": 0,
- "occurrences_sent": 0,
- "discord_message_id": "string",
- "message_url": "string",
- "sent_at": "string",
- "failure_reason": "string",
- "failure_code": "string",
- "audience_count": 0,
- "reaction_count": 0,
- "reply_count": 0,
- "link_click_count": 0,
- "upcoming_occurrences": [
- "2019-08-24T14:15:22Z"
], - "created_at": "string",
- "updated_at": "string"
}
}Delete a scheduled message (or a whole recurring series by its parent id). An already-sent message can be deleted from the record too; it does not unsend it in Discord. A foreign message id reads as 404.
Requires read_write scope. Idempotency-Key is optional (a delete is naturally repeatable); X-Dry-Run: true checks the message exists and is yours without deleting it. Single shop only.
| message_id required | integer (Message Id) >= 1 Message id to delete. |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
{- "data": {
- "ok": true,
- "deleted_id": 0
}, - "dry_run": false
}Whether Reacher is still collecting for a shop, so a consumer can tell "this shop genuinely had no activity" from "we stopped collecting for it" — the two look identical on every other endpoint.
is_collecting is the one-field answer; failure_reason explains a false (banned / account_dead / shop_punished / permission_denied / affiliate_access_missing / seller_account_mismatch / region_not_set / login_issue) and onboarding_status covers shops that never finished setup. region_supported is false for shops in a region Reacher does not operate in — those never collect at all.
shop_id must be one of your own shops. Send x-shop-id: all to poll any of them without re-issuing the request per shop.
| shop_id required | integer (Shop Id) One of your shop IDs. |
{- "shop_id": 0,
- "shop_name": "string",
- "status": "string",
- "is_collecting": true,
- "is_logged_in": true,
- "is_banned": true,
- "failure_reason": "string",
- "onboarding_status": "string",
- "region": "string",
- "detected_region": "string",
- "region_supported": true,
- "last_login_time": "2019-08-24T14:15:22Z"
}Your account's billable seats with live Stripe billing state, so a consumer can answer "is this account still entitled to the data I'm reading?" without screen-scraping the portal.
Each seat carries plan, unit_amount (minor units), status, the live stripe_status, current_period_end, trial_end, cancel_at and access_expires_on — the cancel-grace date after which a canceled seat stops granting access.
Only seats linked to shops this key can reach are returned, plus any unassigned seats (which name no shop). Read collection_method and attempt_count before treating past_due as a payment failure: invoice-billed customers sit in past_due legitimately, and attempt_count == 0 means Stripe never attempted a charge.
Read-only — plan changes and cancellation are portal-only.
{- "has_stripe_customer": true,
- "data": [
- {
- "subscription_item_id": "string",
- "shop_id": 0,
- "shop_name": "string",
- "plan": "string",
- "interval": "string",
- "unit_amount": 0,
- "status": "string",
- "stripe_status": "string",
- "current_period_end": "string",
- "trial_end": "string",
- "cancel_at": "string",
- "access_expires_on": "string",
- "next_payment_attempt": "string",
- "collection_method": "string",
- "attempt_count": 0
}
]
}Confirms the connection works and reports exactly what it can do: the account, the shops in scope, and the granted scopes.
shops is the shops this KEY can reach, which is not always what the account owns — a connector authorized for two shops reports two. Read scopes before attempting a write: a key without read_write gets a 403 from every write endpoint.
Cheap and side-effect free, so it is the right first call to verify a connection rather than a real query that might fail for its own reasons.
{- "customer_id": 0,
- "shops": [
- {
- "shop_id": 0,
- "shop_name": "string"
}
], - "shop_count": 0,
- "scopes": [
- "string"
], - "can_write": true
}The per-category limits actually in force for each of your shops — CRM, automations and email automations — with any support-granted Extra Limits already folded in. This is the resolved number, not the advertised plan number, so a consumer can size a batch before it gets clamped.
limits is category → limit type → value; the types are typically default (what a new automation is created with), minimum and maximum (the enforced ceiling). A category missing for a tier means the plan expresses no opinion, not a limit of zero.
Covers every shop in x-shop-id — send all for the whole account.
{- "data": [
- {
- "shop_id": 0,
- "shop_name": "string",
- "plan": "string",
- "limits": {
- "property1": {
- "property1": 0,
- "property2": 0
}, - "property2": {
- "property1": 0,
- "property2": 0
}
}
}
]
}Your account's billing history straight from Stripe — settled invoices plus the previewed upcoming charge for each live subscription — newest first. Drafts and voided invoices are excluded: they are not real payment history.
Read live from Stripe by customer rather than from Reacher's webhook-populated tables, so it cannot go stale when a webhook is missed. amount is in the minor units of the customer's Stripe billing currency (the currency itself is not carried on this surface). invoice_pdf links are Stripe-hosted and time-limited.
Account-scoped: invoices bill the account, not an individual shop, so the result is the same for any x-shop-id.
{- "has_stripe_customer": true,
- "data": [
- {
- "id": "string",
- "amount": 0,
- "status": "string",
- "due_date": "string",
- "billing_month": "string",
- "plans": {
- "property1": 0,
- "property2": 0
}, - "invoice_pdf": "string"
}
]
}Today's email send capacity for a single shop — the email twin of GET /automations/outreach-capacity — so a consumer can pre-check before queuing email outreach and schedule the remainder for tomorrow.
emails_sent_today counts today's sends; daily_limit is the denominator the portal's daily-send bar uses, derived from the shop's effective plan entitlement.
tier_limits is that entitlement in full (default / minimum / maximum, Extra Limits grants applied). Note the enforced cap is per email automation, stored on the automation itself and clamped into this range when it is written — so a shop running several automations can exceed daily_limit in aggregate. A null field means the tier expresses no such limit, not zero.
Requires a single shop.
{- "shop_id": 0,
- "emails_sent_today": 0,
- "daily_limit": 0,
- "percent_used": 0,
- "tier_limits": {
- "default": 0,
- "minimum": 0,
- "maximum": 0
}
}The retention grid for one shop: creators grouped by the month they first posted (rows), tracked across month-offsets M0..M6 (columns). Each cell carries raw counts — posted (creators who posted), gmv_count (creators who drove GMV) and total_videos — so a consumer derives % Posting / % Driving GMV / videos-per-creator itself. Omit product_id for the latest all-products snapshot, or pass one to live-aggregate that product's cohorts. Single-shop only.
Product Id (string) or Product Id (null) (Product Id) Optional product filter; omit for the all-products grid. |
{- "shop_id": 0,
- "product_id": "string",
- "computed_date": "2019-08-24",
- "offsets": [
- 0
], - "cohorts": [
- {
- "cohort_month": "2019-08-24",
- "affiliates": 0,
- "cells": [
- {
- "offset": 0,
- "posted": 0,
- "gmv_count": 0,
- "total_videos": 0
}
]
}
]
}Drill-down for one clicked cell (cohort × offset): the creators it represents. direction=active returns the creators who qualified at that offset (the cell's numerator); direction=lapsed returns the cohort members who did NOT — the re-engagement target list. Capped at 500 rows (capped flags truncation). Single-shop only.
| cohort_month required | string <date> (Cohort Month) Cohort start month (YYYY-MM-01). |
| offset required | integer (Offset) [ 0 .. 6 ] Months since start (M0..M6). |
| metric | string (Metric) ^(posting|gmv)$ Default: "posting" Which cell was clicked: 'posting' or 'gmv'. |
| direction | string (Direction) ^(active|lapsed)$ Default: "active" 'active' (qualified) or 'lapsed' (re-engagement targets). |
Product Id (string) or Product Id (null) (Product Id) Optional product filter (match the grid). |
{- "creators": [
- {
- "creator_key": "string",
- "creator_display_name": "string",
- "videos_posted": 0,
- "gmv": "0"
}
], - "capped": false,
- "cap": 500,
- "direction": "active"
}First-touch attribution for one cohort: which automations first reached its creators, with each automation's creators_sourced and share of the cohort. Single-shop only.
| cohort_month required | string <date> (Cohort Month) Cohort start month (YYYY-MM-01). |
Product Id (string) or Product Id (null) (Product Id) Optional product filter (match the grid). |
{- "cohort_month": "2019-08-24",
- "cohort_size": 0,
- "attributed": 0,
- "unattributed": 0,
- "automations": [
- {
- "automation_id": 0,
- "automation_name": "string",
- "automation_type": "string",
- "creators_sourced": 0,
- "share": 0,
- "first_run": "2019-08-24",
- "last_run": "2019-08-24"
}
]
}Buckets the shop's recent affiliates by repost recency — actively-reposting / posted-once / lapsed-reposter — over a recency window_days. Buckets genuinely shift with the window. Single-shop only.
| window_days | integer (Window Days) Default: 30 Recency window separating actively-reposting from lapsed. |
{- "window_days": 0,
- "lookback_days": 120,
- "population": 0,
- "reengageable": 0,
- "buckets": [
- {
- "bucket": "string",
- "creators": 0,
- "posts": 0
}
]
}Resolves a segment — a cohort cell (source=cohort_cell) or a repost bucket set (source=repost) — to the underlying creator handles, for exporting or piping into outreach. Single-shop only.
| source required | string (Source) ^(cohort_cell|repost)$ |
Cohort Month (string) or Cohort Month (null) (Cohort Month) cohort_cell only: cohort start month. | |
Offset (integer) or Offset (null) (Offset) cohort_cell only: month offset. | |
| metric | string (Metric) ^(posting|gmv)$ Default: "posting" |
| direction | string (Direction) ^(active|lapsed)$ Default: "lapsed" |
Buckets (string) or Buckets (null) (Buckets) repost only: comma-separated bucket keys. | |
| window_days | integer (Window Days) Default: 30 repost only: recency window. |
{- "handles": [
- "string"
], - "total": 0,
- "resolved": 0,
- "unresolved": 0,
- "capped": false,
- "cap": 10000
}The complete affiliate performance report for one shop over a date window — the same payload the Reacher portal renders and exports.
Includes affiliate GMV, units sold, GMV per video and GMV per sample (each with the previous equal-length period and its % change); recruitment counters (creators reached, sample requests, samples approved, DM / TC / email reply rates); performance counters (videos posted, video views, creators posting, creators converting); the creator-level distribution (L0-L7); six top-product leaderboards (GMV, units, sample requests, approvals, conversions, growth); top creators; top videos; and the rule-based recommendations.
Single-shop only — set x-shop-id to a specific shop ID. The window defaults to the last 7 settled days and is capped at 90 days; end_date must be at least 1 day in the past. An end_date past the shop's newest settled day is clamped to it, which can make the window shorter than requested (the 7-day default included) — the response's start_date/end_date always report the window actually used, and the period-over-period comparison follows that same length. Returns 404 when the shop has no activity in the settled part of the window, and when the requested window lies entirely past the settled day — that message names the newest day the shop is reportable through, so a caller can retry against a window that works.
Start Date (string) or Start Date (null) (Start Date) First day of the window (inclusive). Defaults to 7 days before | |
End Date (string) or End Date (null) (End Date) Last day of the window (inclusive). Must be at least 1 day in the past — today is never fully settled. Defaults to yesterday. |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24"
}{- "id": 0,
- "shop_id": 0,
- "shop_name": "string",
- "customer_id": 0,
- "start_date": "string",
- "end_date": "string",
- "currency_symbol": "$",
- "affiliate_gmv": 0,
- "affiliate_gmv_prev_week": 0,
- "affiliate_gmv_pct_change": 0,
- "gmv_per_video": 0,
- "gmv_per_video_prev_week": 0,
- "gmv_per_video_pct_change": 0,
- "gmv_per_sample": 0,
- "gmv_per_sample_prev_week": 0,
- "gmv_per_sample_pct_change": 0,
- "units_sold": 0,
- "units_sold_prev_week": 0,
- "units_sold_pct_change": 0,
- "total_creators_reached": 0,
- "total_sample_requests": 0,
- "total_samples_approved": 0,
- "creators_added_to_showcase": 0,
- "dm_reply_rate": 0,
- "tc_reply_rate": 0,
- "email_open_rate": 0,
- "email_reply_rate": 0,
- "videos_posted": 0,
- "videos_posted_prev_week": 0,
- "videos_posted_pct_change": 0,
- "video_views": 0,
- "creators_posting_videos": 0,
- "creators_converting": 0,
- "creators_converting_prev_week": 0,
- "creators_converting_pct_change": 0,
- "videos_generating_sales": 0,
- "creator_levels": {
- "l0": 0,
- "l1": 0,
- "l2": 0,
- "l3": 0,
- "l4": 0,
- "l5": 0,
- "l6": 0,
- "l7": 0
}, - "top_products_by_gmv": [ ],
- "top_products_by_units": [ ],
- "top_products_by_sample_requests": [ ],
- "top_products_by_approvals": [ ],
- "top_products_by_conversions": [ ],
- "top_products_by_growth": [ ],
- "top_creators": [ ],
- "top_videos": [ ],
- "recommendations": [ ]
}The recommended-actions block of the weekly report on its own — cheaper than the full report when that is all you need.
Each action carries a type (e.g. reactivate_creators, amplify_videos, prioritize_products), a headline title, a one-line description, and the items it refers to (creator, video or product, each with a display label, image and a formatted metric). Generation is rule-based over the same top-N data the report uses.
Single-shop only — set x-shop-id to a specific shop ID. The window defaults to the last 7 settled days and is capped at 90 days. An empty actions list is a normal 200 for a shop with no qualifying activity.
Start Date (string) or Start Date (null) (Start Date) First day of the window (inclusive). Defaults to 7 days before | |
End Date (string) or End Date (null) (End Date) Last day of the window (inclusive). Must be at least 1 day in the past — today is never fully settled. Defaults to yesterday. |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24"
}{- "shop_id": 0,
- "actions": [ ]
}One row per shop for the window — affiliate GMV, videos posted, video views, videos converted, video conversion rate, active creators, GMV per video and GMV per sample, each with its % change against the previous equal-length period. This is the portal's Agency View table.
Covers every shop this API key can access, so it works with x-shop-id: all, a comma-separated list, or a single shop ID — a key scoped to a subset of shops only ever sees that subset. Sortable by any of: affiliate_gmv, videos_posted, video_views, active_creators, gmv_per_video, gmv_per_sample, video_conversion_rate, shop_name; search filters on shop name; results are paginated (page / page_size, max 100) and total_pagination is the number of shops you can see after filtering.
The window defaults to the last 7 settled days.
Start Date (string) or Start Date (null) (Start Date) First day of the window (inclusive). Defaults to 7 days back. | |
End Date (string) or End Date (null) (End Date) Last day of the window (inclusive). Defaults to yesterday. | |
| page | integer (Page) >= 1 Default: 1 1-based page number. |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 10 Rows per page (max 100). |
| sort_field | string (Sort Field) Default: "affiliate_gmv" Column to sort by. One of: affiliate_gmv, videos_posted, video_views, active_creators, gmv_per_video, gmv_per_sample, video_conversion_rate, shop_name. |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" 'asc' or 'desc'. |
Search (string) or Search (null) (Search) Case-insensitive shop-name filter. |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "page": 1,
- "page_size": 10,
- "sort_field": "affiliate_gmv",
- "sort_order": "desc",
- "search": "string"
}{- "data": [
- {
- "shop_id": 0,
- "shop_name": "string",
- "affiliate_gmv": 0,
- "affiliate_gmv_pct_change": 0,
- "videos_posted": 0,
- "videos_posted_pct_change": 0,
- "video_views": 0,
- "video_views_pct_change": 0,
- "videos_converted": 0,
- "video_conversion_rate": 0,
- "video_conversion_rate_pct_change": 0,
- "active_creators": 0,
- "active_creators_pct_change": 0,
- "gmv_per_video": 0,
- "gmv_per_video_pct_change": 0,
- "gmv_per_sample": 0,
- "gmv_per_sample_pct_change": 0,
- "currency_symbol": "$"
}
], - "total_pagination": 0,
- "page": 0,
- "page_size": 0
}The shop's My Creators table: one row per creator (the most recently updated of their creator × product records), carrying the sample / content lifecycle status, lifetime and 28-day GMV, commission, fulfillment, video and live counts, and CRM tags.
Filter with any combination of status (repeatable display labels), stage (a canonical funnel stage, which overrides status), product_id, tags (creator carries ANY), tags_all (carries ALL) and videos_posted_min (creators with at least N posted videos), plus creator_handle for a case-insensitive handle search. Sort with sort_by / sort_dir; the default is GMV descending.
To COUNT the creators matching a filter without paging the whole roster, request page_size=1 and read pagination.total_count. For example status=Content Unfulfilled returns the number of distinct creators currently marked Content Unfulfilled, and videos_posted_min=2 returns the number who have posted two or more videos. This distinct-creator Content Unfulfilled count is the figure the portal's My Creators view shows; GET /funnel also reports a Content Unfulfilled stage, but that funnel bucket counts creator-and-product pairs and can be larger, so use this endpoint for the per-creator KPI.
start_date/end_date window the metric columns (video_gmv, total_video_views, videos_posted) — pass both or neither. Status, tags, lifetime gmv and the videos_posted_min filter are always as-of-now: videos_posted_min counts a creator's lifetime posts and is NOT narrowed by the window, so with a window set it can return a creator whose windowed videos_posted column reads lower than the threshold.
Set include_products=true to attach each creator's product cards (title, image, status, remaining sample stock). Per-product GMV and the videos themselves are deliberately not attached — they are the two heaviest reads in this surface; fetch them per creator from GET /crm/content.
Changed 2026-09: the roster returns every creator filed under this shop. It previously dropped anyone whose row in Reacher's global creator registry had been marked inactive — an outreach-side, cross-shop flag that hid creators this shop had earned GMV from. Expect more rows than before on shops with long CRM histories; no field or filter semantics changed.
Single-shop only. Creator contact details are never returned: no real name, shipping address, phone or email. creator_name is the public TikTok handle.
| page | integer (Page) Default: 1 1-based page number. |
| page_size | integer (Page Size) Default: 50 Rows per page (max 100). |
Array of Status (strings) or Status (null) (Status) Repeatable status label. One of: ['Sample Requested', 'Sample Approved', 'Ready to Ship', 'Sample Shipped', 'Content Pending', 'Completed', 'Sample Request Rejected', 'Sample Request Expired', 'Content Unfulfilled', 'Idle', 'Posted Video', 'Sold Products', 'GMV Generated', 'Showcasing Product', 'Livestreamed']. NOTE: ['Posted Video', 'Sold Products', 'GMV Generated', 'Showcasing Product', 'Livestreamed'] are ACTIVITY-METRIC filters, not pipeline-status filters — they match on sales/GMV/video/livestream activity regardless of the creator's curr_status, so a returned row's status will NOT equal one of these labels (e.g. 'Sold Products' can return a 'Showcasing Product' creator who has sales). The other labels filter on curr_status directly. | |
Stage (string) or Stage (null) (Stage) Canonical funnel stage id; overrides | |
Product Id (string) or Product Id (null) (Product Id) Restrict to one product. | |
Array of Tags (strings) or Tags (null) (Tags) Repeatable. Creators carrying ANY of these CRM tags. | |
Array of Tags All (strings) or Tags All (null) (Tags All) Repeatable. Creators carrying ALL of these CRM tags. | |
Videos Posted Min (integer) or Videos Posted Min (null) (Videos Posted Min) Only include creators who have posted at least this many videos for this shop — the same 'Videos Posted' threshold the portal's My Creators filter uses. Counts lifetime posts (as-of-now); | |
Creator Handle (string) or Creator Handle (null) (Creator Handle) Case-insensitive substring search on the TikTok handle. | |
Sort By (string) or Sort By (null) (Sort By) Sort column. One of: ['video_num', 'live_num', 'fulfillment_rate', 'gmv', 'video_release_date', 'creator_gmv', 'follower_num', 'video_gmv', 'total_video_views', 'videos_posted', 'avg_views', 'affiliate_gmv_28d', 'units_sold', 'commission']. | |
| sort_dir | string (Sort Dir) Default: "desc" Enum: "asc" "desc" Sort direction. |
Start Date (string) or Start Date (null) (Start Date) Start of the metric window. Requires end_date. | |
End Date (string) or End Date (null) (End Date) End of the metric window. Requires start_date. | |
| include_products | boolean (Include Products) Default: false Attach each creator's product cards. |
{- "data": [
- {
- "id": "string",
- "creator_id": "string",
- "creator_name": "string",
- "avatar_url": "string",
- "follower_num": 0,
- "tags": [
- "string"
], - "bio": "string",
- "categories": [
- "string"
], - "curr_status": "string",
- "status": "string",
- "expired_in": 0,
- "fulfillment_status": 0,
- "fulfillment_rate": 0,
- "sample_received_num": 0,
- "product_id": "string",
- "product_title": "string",
- "sku_id": "string",
- "sku_left_num": 0,
- "gmv": 0,
- "creator_gmv": 0,
- "video_gmv": 0,
- "affiliate_gmv_28d": 0,
- "refund_gmv": 0,
- "commission": 0,
- "commission_rate": 0,
- "estimate_commission": 0,
- "units_sold": 0,
- "item_sold": 0,
- "refund_item_sold": 0,
- "product_sold": 0,
- "video_num": 0,
- "live_num": 0,
- "videos_posted": 0,
- "total_video_views": 0,
- "content_video_views": 0,
- "avg_views": 0,
- "engagement_rate": 0,
- "video_release_date": "2019-08-24T14:15:22Z",
- "video_url": "string",
- "updated_at": "2019-08-24T14:15:22Z",
- "products": [
- {
- "product_id": "string",
- "product_title": "string",
- "sku_image": "string",
- "status": "string",
- "requested_date": "string",
- "expires_in": 0,
- "quantity": 0
}
]
}
], - "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0
}
}The Creator Profile drawer for one roster creator: identity and follower count, top products by GMV, shop performance (total / video / live GMV, units sold, videos and lives posted), affiliate performance (28-day GMV, engagement rate, average views, post rate, brands worked with, PPS score), the current sales-metric cards, and the CRM Groups the creator belongs to.
creator_id is the value from GET /crm/creators. The profile is resolved through the creator's handle, so it spans every creator_id the CRM, content and affiliate syncs filed the creator under — a creator whose content landed under a second id still reports their full video GMV here.
Inferred demographics (gender, age range, ethnicity, likely location) shown in the portal are omitted. Single-shop only. Creator contact details are never returned: no real name, shipping address, phone or email. creator_name is the public TikTok handle.
| creator_id required | string (Creator Id) <= 255 characters Creator id from GET /crm/creators. |
{- "data": {
- "creator_id": "string",
- "creator_name": "string",
- "handle": "string",
- "avatar_url": "string",
- "tiktok_url": "string",
- "tags": [
- "string"
], - "follower_num": 0,
- "creator_level": "string",
- "shop_gmv": 0,
- "affiliate_gmv_28d": 0,
- "creator_gmv": 0,
- "has_shop_data": true,
- "top_products": [
- { }
], - "shop_performance": { },
- "affiliate_performance": { },
- "sales_metrics": { },
- "groups": [
- { }
]
}
}Every piece of content one creator posted for this shop, per product, with views, likes, comments and paid_orders_count — the attributed-order count TikTok reports for that video. Reads analytics.crm_content_data, the same source behind the portal's expanded creator row.
creator_id is required (this is a per-creator drill-down, not a shop-wide content feed — use GET /videos for that); narrow further with product_id. Ordered newest first.
Product titles are not returned: the underlying join resolves a single title per creator rather than per product, so it would be wrong on any creator working on more than one. Join product_id against GET /crm/creators for titles.
Single-shop only. Creator contact details are never returned: no real name, shipping address, phone or email. creator_name is the public TikTok handle.
| creator_id required | string (Creator Id) <= 255 characters Creator id from GET /crm/creators. |
Product Id (string) or Product Id (null) (Product Id) Restrict to content for one product. | |
| page | integer (Page) Default: 1 1-based page number. |
| page_size | integer (Page Size) Default: 50 Items per page (max 100). |
{- "data": [
- {
- "content_id": "string",
- "creator_id": "string",
- "product_id": "string",
- "content_type": "string",
- "content_desc": "string",
- "content_url": "string",
- "thumbnail_url": "string",
- "views": 0,
- "likes": 0,
- "comments": 0,
- "paid_orders_count": 0,
- "posted_time": "2019-08-24T14:15:22Z"
}
], - "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0
}
}The audit log of lifecycle changes for one creator on this shop, from customers.crm_creator_status_history — each row is a previous_status → new_status move with the timestamp the sync detected it, plus display labels for both. Use it to measure how long creators sit in a stage, or to reconstruct when a sample was approved, shipped or went unfulfilled.
The grain is (creator, product): a creator collaborating on three products has three independent transition chains, so filter by product_id to follow one. Ordered newest first.
Single-shop only. Creator contact details are never returned: no real name, shipping address, phone or email. creator_name is the public TikTok handle.
| creator_id required | string (Creator Id) <= 255 characters Creator id from GET /crm/creators. |
Product Id (string) or Product Id (null) (Product Id) Restrict to one product's chain. | |
| page | integer (Page) Default: 1 1-based page number. |
| page_size | integer (Page Size) Default: 50 Transitions per page (max 200). |
{- "data": [
- {
- "product_id": "string",
- "previous_status": "string",
- "previous_status_label": "string",
- "new_status": "string",
- "new_status_label": "string",
- "transition_detected_at": "2019-08-24T14:15:22Z"
}
], - "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0
}
}The prerequisite call: which of this shop's four data channels are wired up, and which have actually landed data. Correlations are only computed for channels flagged here, so a null amazon correlation on a shop with amazon: false is expected, not a failure.
amazon / shopify mean an active authorization exists; the matching *_has_data flags mean rows have arrived. Connected but not-yet-populated is the normal state for the first few hours after a brand links a store. Single-shop only.
{- "shop_id": 0,
- "tiktok": true,
- "amazon": true,
- "shopify": true,
- "branded_search": true,
- "amazon_has_data": true,
- "shopify_has_data": true
}Every Amazon ASIN and Shopify product this shop has off-platform revenue rows for — the value set for the amazon_asins and shopify_product_ids filters on the timeseries, analysis and branded-search endpoints. Omit channel to get both catalogs in one call; a catalog the shop has no data for comes back as an empty list.
There is no TikTok catalog: TikTok halo inputs (views, GMV) are only stored shop-wide, so TikTok is never product-filterable and passing channel=tiktok is rejected. search substring-matches ASIN and product name case-insensitively. Single-shop only.
Channel (string) or Channel (null) (Channel) Limit to one channel; omit to get both catalogs. | |
Search (string) or Search (null) (Search) Case-insensitive substring match on ASIN / product name. | |
| limit | integer (Limit) [ 1 .. 2000 ] Default: 500 Max rows per channel. |
{- "shop_id": 0,
- "amazon": [
- {
- "asin": "string",
- "marketplace_id": "string"
}
], - "shopify": [
- {
- "product_id": "string",
- "product_name": "string"
}
]
}Every halo channel on one contiguous date axis: TikTok views and GMV, Amazon revenue, Shopify revenue, branded-search volume and click share. dates[i] indexes every other array, so the arrays can be plotted or diffed element-wise without re-joining on date.
A null means no data for that day; a 0 means a real zero — the distinction is preserved deliberately, and channels the shop hasn't connected are null throughout. Pass amazon_asins / shopify_product_ids to scope revenue (and branded search) to specific products; omit them for shop totals.
TikTok points are only daily-resolved for the most recent ~30 days — beyond that TikTok's stored series is monthly-bucketed and cannot be aligned to days, so tiktok_views / tiktok_gmv come back null for the out-of-coverage tail while the revenue series stay complete. Defaults to the last 30 days ending yesterday; max 366. Single-shop only.
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). Defaults to 29 days before | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). Defaults to yesterday — today's revenue rows have not been pulled yet. | |
Array of Amazon Asins (strings) or Amazon Asins (null) (Amazon Asins) Restrict Amazon revenue and branded search to these ASINs. Omit for the shop total. Values come from | |
Array of Shopify Product Ids (strings) or Shopify Product Ids (null) (Shopify Product Ids) Restrict Shopify revenue to these product IDs. Omit for the shop total. Values come from |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "amazon_asins": [
- "string"
], - "shopify_product_ids": [
- "string"
]
}{- "shop_id": 0,
- "start_date": "string",
- "end_date": "string",
- "dates": [
- "string"
], - "tiktok_views": [
- 0
], - "tiktok_gmv": [
- 0
], - "amazon_revenue": [
- 0
], - "shopify_revenue": [
- 0
], - "branded_search_terms": [
- 0
], - "branded_search_click_share": [
- 0
]
}The flagship halo read — how much off-TikTok revenue moved with TikTok activity over the window, and how confidently that can be claimed. Four things come back:
summary — base_sales is the average daily Amazon+Shopify revenue over the 30 days immediately BEFORE the window (the pre-period run rate). incremental_revenue is the window's revenue minus that run rate carried across the days that have data — the revenue above baseline, and the number to quote as halo lift. It goes negative when the window underperformed the prior 30 days. relationship_strength is the Pearson r between daily TikTok views and daily combined off-platform revenue; views_explain is r squared, readable as 'views explain N% of the day-to-day revenue swing'.
channel_correlations — the same regression per off-platform channel, each with r, a two-sided p_value (below 0.05 is the usual bar for calling it real) and a plain-English label. TikTok GMV is deliberately absent: it is driven by the same views it would be correlated against, so it correlates by construction and is direct attribution, not halo. It appears under attribution instead.
branded_search_correlation — TikTok views vs Amazon branded search, the earliest and cleanest halo signal. Null when the shop has no branded-search tracking.
attribution — raw revenue sums per channel for the window, unmodelled.
lag_days shifts TikTok views forward before correlating, so a video today can be matched against an Amazon order three days later; sweep 0-14 and keep the strongest r. Any r is null when fewer than 5 aligned day-pairs have data on both sides, or when a channel is not connected — check GET /halo/channels first. Correlation is not causation: a strong r with a plausible lag is evidence, not proof.
Defaults to the last 30 days ending yesterday; max 366, though TikTok views are only daily-resolved for the most recent ~30 days, so wider windows weaken the regression rather than strengthen it. Single-shop only.
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). Defaults to 29 days before | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). Defaults to yesterday — today's revenue rows have not been pulled yet. | |
Array of Amazon Asins (strings) or Amazon Asins (null) (Amazon Asins) Restrict Amazon revenue and branded search to these ASINs. Omit for the shop total. Values come from | |
Array of Shopify Product Ids (strings) or Shopify Product Ids (null) (Shopify Product Ids) Restrict Shopify revenue to these product IDs. Omit for the shop total. Values come from | |
| lag_days | integer (Lag Days) [ 0 .. 14 ] Default: 0 Shift TikTok views forward N days before correlating, to test a delayed halo (a video today, an Amazon order three days later). 0 correlates same-day. Sweep 0-14 and keep the strongest r. |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "amazon_asins": [
- "string"
], - "shopify_product_ids": [
- "string"
], - "lag_days": 0
}{- "shop_id": 0,
- "start_date": "string",
- "end_date": "string",
- "lag_days": 0,
- "summary": {
- "base_sales": 0,
- "incremental_revenue": 0,
- "relationship_strength": 0,
- "views_explain": 0
}, - "channel_correlations": [
- {
- "channel": "string",
- "r": 0,
- "p_value": 0,
- "label": "string"
}
], - "branded_search_correlation": {
- "channel": "string",
- "r": 0,
- "p_value": 0,
- "label": "string"
}, - "attribution": {
- "tiktok_gmv": 0,
- "amazon": 0,
- "shopify": 0,
- "total": 0
}
}Amazon branded search is the cleanest halo signal there is: nobody searches a brand by name unless something put it in their head, and on TikTok-driven brands that something is usually a video. This returns the daily branded-search volume and click share for the window, the window totals, the peak day, and the Pearson correlation against TikTok views at the requested lag_days — the same regression the analysis endpoint runs.
top_search_terms is always null: Amazon reports branded-search volume per ASIN per day, not the query strings, so there is no term list to return. The field exists so it can be filled later without a breaking change.
Pass asins to scope to specific products; omit for every tracked ASIN. An empty series means branded search isn't tracked for this shop (check branded_search on GET /halo/channels). Single-shop only.
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). | |
Array of Asins (strings) or Asins (null) (Asins) Restrict to these ASINs. Omit for every tracked ASIN. | |
| lag_days | integer (Lag Days) [ 0 .. 14 ] Default: 0 Shift TikTok views forward N days before correlating. |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "asins": [
- "string"
], - "lag_days": 0
}{- "shop_id": 0,
- "start_date": "string",
- "end_date": "string",
- "lag_days": 0,
- "asins": [
- "string"
], - "total_search_terms": 0,
- "average_click_share": 0,
- "peak_date": "string",
- "peak_search_terms": 0,
- "correlation": {
- "channel": "string",
- "r": 0,
- "p_value": 0,
- "label": "string"
}, - "top_search_terms": [
- "string"
], - "series": [
- {
- "date": "string",
- "search_terms": 0,
- "click_share": 0
}
]
}Set or update the goal values the shop is measured against for one granularity — the numbers behind the dashboard's progress bars.
Upsert semantics: if the shop already has targets for the given granularity, the most recent set is updated in place; otherwise a new set is created. Only the metrics you send are stored, so send the full set you want in effect rather than a partial diff.
Available metrics: target_collabs, creators_messaged, sample_requests, sample_approved, videos_posted, videos_converted, spark_codes. At least one is required, and each must be a non-negative integer.
Read the current targets back with GET /targets. Requires a key with read_write scope and a single shop.
| granularity required | string (GranularityEnum) Enum: "weekly" "monthly" "quarterly" Period the goals apply to: weekly, monthly or quarterly. |
required | object (TargetMetricsInput) Per-metric goal values. Omit a metric to leave it unset; at least one must be provided. |
Effective From (string) or Effective From (null) (Effective From) When the goals start applying. Defaults to now. Cannot be more than a year in the future. |
{- "granularity": "monthly",
- "targets": {
- "creators_messaged": 200,
- "target_collabs": 50,
- "videos_posted": 35
}
}{- "created": true,
- "target": {
- "created_at": "2024-01-01T00:00:00Z",
- "customer_id": 924,
- "granularity": "monthly",
- "id": "550e8400-e29b-41d4-a716-446655440000",
- "shop_id": 1183,
- "targets": {
- "creators_messaged": 200,
- "sample_approved": 80,
- "sample_requests": 100,
- "spark_codes": 10,
- "target_collabs": 50,
- "videos_converted": 25,
- "videos_posted": 35
}, - "updated_at": "2024-01-01T00:00:00Z"
}
}Convenience fan-out of PUT /targets — set weekly, monthly and quarterly goals in one request instead of three. Each entry is upserted exactly as the single-granularity route does, and each granularity may appear only once.
NOT atomic: entries are applied in order and an invalid one aborts the request, leaving earlier entries already written. Re-sending the whole payload after fixing the bad entry is safe — writes are upserts. Requires a key with read_write scope and a single shop.
required | Array of objects (Targets) [ 1 .. 10 ] items Up to 10 target sets, one per granularity. Repeating a granularity is rejected. |
{- "targets": [
- {
- "granularity": "monthly",
- "targets": {
- "creators_messaged": 200,
- "target_collabs": 50,
- "videos_posted": 35
}
}
]
}{- "results": [
- {
- "created": true,
- "target": {
- "created_at": "2024-01-01T00:00:00Z",
- "customer_id": 924,
- "granularity": "monthly",
- "id": "550e8400-e29b-41d4-a716-446655440000",
- "shop_id": 1183,
- "targets": {
- "creators_messaged": 200,
- "sample_approved": 80,
- "sample_requests": 100,
- "spark_codes": 10,
- "target_collabs": 50,
- "videos_converted": 25,
- "videos_posted": 35
}, - "updated_at": "2024-01-01T00:00:00Z"
}
}
]
}The creators this shop has blacklisted. Outreach automations skip them, so this is the shop's suppression list.
Handles are returned normalized (lowercased, no leading @) — that is the form DELETE /shops/blacklist/{creator} expects. Each row also carries the stable id and when it was added. Single-shop only.
{- "creators": [
- {
- "id": 0,
- "creator": "string",
- "added_at": "string"
}
], - "total_count": 0
}Blacklist one or more creators so outreach automations stop targeting them.
Handles are normalized before storage (a leading @ and surrounding whitespace are stripped, the handle is lowercased), so @Creator and creator are the same entry. The call is additive and forgiving: handles already on the list are counted in duplicates_skipped, malformed handles (URLs, blanks) in invalid_skipped — neither fails the request, so a partially valid batch still applies. Check added_count for what actually changed.
At most 500 handles per request. Requires a key with read_write scope and a single shop.
| creators required | Array of strings (Creators) [ 1 .. 500 ] items Creator handles. A leading |
{- "creators": [
- "@somecreator",
- "another.creator"
]
}{- "added_count": 0,
- "duplicates_skipped": 0,
- "invalid_skipped": 0,
- "invalid_creators": [
- "string"
], - "message": "string"
}Un-blacklist a single creator by handle, making them eligible for outreach again.
The handle is normalized the same way as on add, so @Creator and creator both resolve to the same entry. A handle that is not on this shop's list returns 404 — this route is not a silent no-op, so a typo surfaces instead of being mistaken for a successful removal. To empty the whole list use POST /shops/blacklist/clear.
Requires a key with read_write scope and a single shop.
| creator required | string (Creator) <= 100 characters Creator handle to un-blacklist; a leading |
{- "removed_count": 0,
- "creator": "string"
}Remove every creator from this shop's blacklist in one call, making all of them eligible for outreach again.
There is no undo — the rows are deleted, not archived, so read the list with GET /shops/blacklist first if you may need to restore it. Clearing an already-empty list is a no-op that returns removed_count: 0. Requires a key with read_write scope and a single shop.
{- "removed_count": 0
}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.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| name required | string (Name) [ 1 .. 120 ] characters Human-readable list name shown in the portal. |
Description (string) or Description (null) (Description) | |
| creator_handles | Array of strings (Creator Handles) <= 50000 items TikTok handles, with or without leading '@'. Deduplicated case-insensitively at insert time. |
| creator_emails | Array of strings (Creator Emails) <= 50000 items Email addresses. Deduplicated case-insensitively at insert time. |
{- "name": "string",
- "description": "string",
- "creator_handles": [
- "string"
], - "creator_emails": [
- "string"
]
}{- "data": {
- "list_id": "string",
- "name": "string",
- "description": "string",
- "creator_count": 0,
- "email_count": 0,
- "state": "active",
- "created_at": "string",
- "updated_at": "string"
}
}| page | integer (Page) Default: 1 |
| page_size | integer (Page Size) Default: 50 |
Name Contains (string) or Name Contains (null) (Name Contains) |
{- "data": [
- {
- "list_id": "string",
- "name": "string",
- "description": "string",
- "creator_count": 0,
- "email_count": 0,
- "state": "active",
- "created_at": "string",
- "updated_at": "string"
}
], - "pagination": null
}| list_id required | integer (List Id) |
{- "data": {
- "list_id": "string",
- "name": "string",
- "description": "string",
- "creator_count": 0,
- "email_count": 0,
- "state": "active",
- "created_at": "string",
- "updated_at": "string"
}, - "creator_handles": [
- "string"
], - "creator_emails": [
- "string"
]
}Sets state=archived. Idempotent — already-archived returns 200.
| list_id required | integer (List Id) |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
nullPaginated 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.
| page | integer (Page) Default: 1 |
| page_size | integer (Page Size) Default: 50 |
Name Contains (string) or Name Contains (null) (Name Contains) |
{- "data": [
- {
- "crm_group_id": "string",
- "name": "string",
- "description": "string",
- "conditions": [
- {
- "field": "string",
- "operator": "string",
- "value": null,
- "value_count": 0
}
], - "creator_count": 0,
- "last_synced_at": "string",
- "members_last_changed_at": "string"
}
], - "pagination": null
}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.
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.
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.
Creation upserts by name. Reusing an existing group_name in this shop overwrites that group's definition and replaces its members.
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.
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.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| group_name required | string (Group Name) [ 1 .. 255 ] characters 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 (string) or Tier (null) (Tier) GMV tier code to narrow the segment, e.g. 'L3'. Omit for all tiers. | |
Automation Id (integer) or Automation Id (null) (Automation Id) Restrict the segment to creators reached by this automation. | |
Start Date (string) or Start Date (null) (Start Date) Segment window start (inclusive). | |
End Date (string) or End Date (null) (End Date) Segment window end (inclusive). | |
Array of Group Tags (strings) or Group Tags (null) (Group Tags) Optional tags to attach to the group. |
{- "group_name": "string",
- "tier": "string",
- "automation_id": 1,
- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "group_tags": [
- "string"
]
}{- "data": {
- "crm_group_id": "string",
- "name": "string",
- "creator_count": 0,
- "handles_resolved": 0,
- "group_tags": [
- "string"
]
}, - "dry_run": true
}| crm_group_id required | string (Crm Group Id) |
{- "data": {
- "crm_group_id": "string",
- "name": "string",
- "description": "string",
- "conditions": [
- {
- "field": "string",
- "operator": "string",
- "value": null,
- "value_count": 0
}
], - "creator_count": 0,
- "last_synced_at": "string",
- "members_last_changed_at": "string"
}
}The shop's videos with their usage-rights status (requested, accepted, active, declined, …), filterable by status, search, date, GMV/views, creators, products, and spoken-transcript keywords. Ordered and paginated. Single shop only (set x-shop-id).
Status (string) or Status (null) (Status) | |
Search (string) or Search (null) (Search) | |
Sort By (string) or Sort By (null) (Sort By) | |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 10 |
Date From (string) or Date From (null) (Date From) | |
Date To (string) or Date To (null) (Date To) | |
Gmv Min (number) or Gmv Min (null) (Gmv Min) | |
Gmv Max (number) or Gmv Max (null) (Gmv Max) | |
Views Min (integer) or Views Min (null) (Views Min) | |
Views Max (integer) or Views Max (null) (Views Max) | |
Creators (string) or Creators (null) (Creators) Comma-separated creator handles. | |
Product Ids (string) or Product Ids (null) (Product Ids) Comma-separated TikTok product ids. A video matches when the product is the one it is listed under, or appears anywhere in its product anchors. | |
| in_library | string (In Library) ^(in|out|all)$ Default: "all" |
Transcript Keywords (string) or Transcript Keywords (null) (Transcript Keywords) Comma/newline-separated phrases matched against the spoken transcript. | |
| transcript_mode | string (Transcript Mode) ^(exclude|only)$ Default: "exclude" |
| transcript_scan_caption | boolean (Transcript Scan Caption) Default: false |
| archived | string (Archived) ^(hide|only|all)$ Default: "hide" |
{- "data": { }
}One usage-rights record by id, scoped to this shop. Returns 404 if the record doesn't exist or belongs to another shop. Single shop only.
| usage_right_id required | string (Usage Right Id) [ 1 .. 36 ] characters |
{- "data": {
- "id": "string",
- "videoId": "string",
- "shopId": 0,
- "creatorId": "string",
- "creatorAccountId": 0,
- "messageText": "string",
- "isPaidOffer": false,
- "offerAmount": 0,
- "currency": "USD",
- "usageRightsMonths": 0,
- "usageRightsScope": "string",
- "usageExclusivityEnabled": true,
- "usageExclusivityMonths": 0,
- "agreementClause": "string",
- "status": "",
- "dmStatus": "pending",
- "dmSentAt": "string",
- "dmError": "string",
- "gcsVideoPath": "string",
- "respondedAt": "string",
- "cancelledAt": "string",
- "createdAt": "",
- "updatedAt": ""
}
}Create a usage-rights request for one of the shop's videos and send the creator the outreach DM. The video must be the shop's own; the creator is canonicalised from the live video owner (a mismatched creatorId is rejected). If the DM can't be delivered the request is rolled back and 422 DM_NOT_DELIVERED is returned — so the call is all-or-nothing. Requires read_write scope and an Idempotency-Key. Single shop only.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) |
Usagerightsmonths (integer) or Usagerightsmonths (null) (Usagerightsmonths) | |
Usagerightsscope (string) or Usagerightsscope (null) (Usagerightsscope) | |
Usageexclusivityenabled (boolean) or Usageexclusivityenabled (null) (Usageexclusivityenabled) | |
Usageexclusivitymonths (integer) or Usageexclusivitymonths (null) (Usageexclusivitymonths) | |
Agreementclause (string) or Agreementclause (null) (Agreementclause) | |
| videoId required | string (Videoid) [ 1 .. 64 ] characters |
| creatorId required | string (Creatorid) [ 1 .. 64 ] characters |
Messagetext (string) or Messagetext (null) (Messagetext) | |
| isPaidOffer | boolean (Ispaidoffer) Default: false |
| offerAmount | number (Offeramount) >= 0 Default: 0 |
{- "usageRightsMonths": 120,
- "usageRightsScope": "organic_only",
- "usageExclusivityEnabled": true,
- "usageExclusivityMonths": 1,
- "agreementClause": "string",
- "videoId": "string",
- "creatorId": "string",
- "messageText": "string",
- "isPaidOffer": false,
- "offerAmount": 0
}{- "data": {
- "id": "string",
- "videoId": "string",
- "shopId": 0,
- "creatorId": "string",
- "creatorAccountId": 0,
- "messageText": "string",
- "isPaidOffer": false,
- "offerAmount": 0,
- "currency": "USD",
- "usageRightsMonths": 0,
- "usageRightsScope": "string",
- "usageExclusivityEnabled": true,
- "usageExclusivityMonths": 0,
- "agreementClause": "string",
- "status": "",
- "dmStatus": "pending",
- "dmSentAt": "string",
- "dmError": "string",
- "gcsVideoPath": "string",
- "respondedAt": "string",
- "cancelledAt": "string",
- "createdAt": "",
- "updatedAt": ""
}
}Cancel a pending usage-rights request (status requested, accepted, or pending_payment). No-op-safe: cancelling an already-terminal record returns 409. Requires read_write scope and an Idempotency-Key. Single shop only.
| usage_right_id required | string (Usage Right Id) [ 1 .. 36 ] characters |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) |
{- "data": {
- "id": "string",
- "videoId": "string",
- "shopId": 0,
- "creatorId": "string",
- "creatorAccountId": 0,
- "messageText": "string",
- "isPaidOffer": false,
- "offerAmount": 0,
- "currency": "USD",
- "usageRightsMonths": 0,
- "usageRightsScope": "string",
- "usageExclusivityEnabled": true,
- "usageExclusivityMonths": 0,
- "agreementClause": "string",
- "status": "",
- "dmStatus": "pending",
- "dmSentAt": "string",
- "dmError": "string",
- "gcsVideoPath": "string",
- "respondedAt": "string",
- "cancelledAt": "string",
- "createdAt": "",
- "updatedAt": ""
}
}Every visible Discovery Agent profile for the shop. A profile is a titled creator brief — a search_query plus optional structured filters (gender, age, ethnicity, region) — that Reacher either generated from the shop's catalog (source = generated) or a user created by hand.
Each row carries id (pass it to the creator listing below), title, description, search_query, filters, creator_count (the size of its last resolved match set), hide_unsafe, is_active, last_search_at and the created/updated timestamps. Hidden and deleted profiles are excluded; has_profiles is false for a shop that has never generated any.
Single-shop only. Read-only — creating, editing and deleting profiles is not exposed on the public API.
{- "shop_id": 0,
- "profiles": [
- {
- "id": 0,
- "title": "string",
- "description": "string",
- "search_query": "string",
- "filters": { },
- "source": "string",
- "is_active": true,
- "vault_file_resource_id": 0,
- "is_hidden": true,
- "is_deleted": true,
- "hide_unsafe": true,
- "creator_count": 0,
- "last_search_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "deleted_at": "2019-08-24T14:15:22Z"
}
], - "has_profiles": true,
- "total_profiles": 0
}Create a new creator brief for the shop — a search_query plus optional demographic filters — and resolve it immediately, so the response carries the creator_count it matched.
The brief is created with source: user_created and shows up in GET /discovery/profiles right away. The stored title is regenerated from the query and filters rather than kept verbatim, so read it back from the response instead of assuming what you sent.
Requires read_write scope and an Idempotency-Key header — a replay of the same key and body returns the original 201 rather than creating a second brief. Single-shop only.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) |
| title required | string (Title) [ 1 .. 255 ] characters Human-readable name for the brief. The stored title is regenerated from |
| search_query required | string (Search Query) non-empty Natural-language description of the creators you want, e.g. 'skincare creators who film get-ready-with-me content'. |
Description (string) or Description (null) (Description) Optional longer note about what this brief is for. | |
Filters (object) or Filters (null) (Filters) Structured demographic filters layered on top of the query — |
{- "title": "string",
- "search_query": "string",
- "description": "string",
- "filters": { }
}{- "success": true,
- "profile": {
- "id": 0,
- "title": "string",
- "description": "string",
- "search_query": "string",
- "filters": { },
- "source": "string",
- "is_active": true,
- "vault_file_resource_id": 0,
- "is_hidden": true,
- "is_deleted": true,
- "hide_unsafe": true,
- "creator_count": 0,
- "last_search_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "deleted_at": "2019-08-24T14:15:22Z"
}, - "creator_count": 0
}A single Discovery Agent profile by ID — the same record the list endpoint returns, fetched directly when you already hold the ID.
Returns 404 when the profile doesn't exist or belongs to a different shop; a profile is only ever visible to the shop that owns it. Unlike the list endpoint this also returns hidden profiles, so a stored ID keeps resolving after a user hides it in the portal.
Single-shop only.
| profile_id required | integer (Profile Id) >= 1 Discovery Agent profile ID. |
{- "id": 0,
- "title": "string",
- "description": "string",
- "search_query": "string",
- "filters": { },
- "source": "string",
- "is_active": true,
- "vault_file_resource_id": 0,
- "is_hidden": true,
- "is_deleted": true,
- "hide_unsafe": true,
- "creator_count": 0,
- "last_search_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "deleted_at": "2019-08-24T14:15:22Z"
}Partially update one brief — only the fields you send are changed.
Changing search_query or filters re-derives the stored title and re-resolves the match set, and if the brief already backs a Vault list that list is re-synced. Editing a Reacher-generated brief flips its source to user_created; it is then yours and is no longer regenerated.
is_deleted is not settable here — use DELETE /discovery/profiles/{profile_id} — so a PATCH can never silently retire a brief.
Returns 404 if the profile does not exist or belongs to another shop. Requires read_write scope. Single-shop only.
| profile_id required | integer (Profile Id) >= 1 Discovery Agent profile ID. |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) |
Title (string) or Title (null) (Title) | |
Description (string) or Description (null) (Description) | |
Search Query (string) or Search Query (null) (Search Query) | |
Filters (object) or Filters (null) (Filters) Replaces the stored demographic filters wholesale. | |
Is Active (boolean) or Is Active (null) (Is Active) Whether the brief is refreshed on the usual schedule. | |
Is Hidden (boolean) or Is Hidden (null) (Is Hidden) Hide the brief from the profile list without deleting it. | |
Hide Unsafe (boolean) or Hide Unsafe (null) (Hide Unsafe) Exclude brand-unsafe creators from the match set. |
{- "title": "string",
- "description": "string",
- "search_query": "string",
- "filters": { },
- "is_active": true,
- "is_hidden": true,
- "hide_unsafe": true
}{- "success": true,
- "profile": {
- "id": 0,
- "title": "string",
- "description": "string",
- "search_query": "string",
- "filters": { },
- "source": "string",
- "is_active": true,
- "vault_file_resource_id": 0,
- "is_hidden": true,
- "is_deleted": true,
- "hide_unsafe": true,
- "creator_count": 0,
- "last_search_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "deleted_at": "2019-08-24T14:15:22Z"
}
}Soft-delete one brief. The row is retained and marked deleted, so the profile disappears from GET /discovery/profiles but any Vault list already saved from it is untouched — deleting the brief never deletes creators you saved.
Returns 404 if the profile does not exist or belongs to another shop. Requires read_write scope. Single-shop only.
| profile_id required | integer (Profile Id) >= 1 Discovery Agent profile ID. |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) |
{- "success": true,
- "message": "string"
}Resolve one profile to its matching creators. The profile's search_query and its structured filters are combined into the search that runs, so this returns exactly what the portal shows under that profile.
This is a read — POST only because the optional filters payload is a nested object that doesn't survive a query string. Nothing is created or modified.
Paginate with page / limit (max 1000 per page); total_creators is the full match count. Sort by any of: relevance_score, follower_count, gmv_num, avg_views, engagement_rate_segment. Supply filters to narrow the set further without editing the profile.
Each creator row carries its handle, follower/GMV/engagement metrics, categories, bio and the relevance_score that ordered it. Creator email addresses are omitted, matching the /ai-search surface.
Returns 404 when the profile doesn't exist or belongs to a different shop. Single-shop only.
| profile_id required | integer (Profile Id) >= 1 Discovery Agent profile ID. |
| page | integer (Page) >= 1 Default: 1 1-based page number. |
| limit | integer (Limit) [ 1 .. 1000 ] Default: 100 Creators per page (max 1000). |
| sort_by | string (Sort By) Default: "relevance_score" Column to sort by. One of: relevance_score, follower_count, gmv_num, avg_views, engagement_rate_segment. |
| sort_order | string (Sort Order) ^(asc|desc)$ Default: "desc" 'asc' or 'desc'. |
Filters (object) or Filters (null) (Filters) Extra filters applied on top of the profile's own query and filters — same shape as the AI-search filter payload, e.g. |
{- "page": 1,
- "limit": 100,
- "sort_by": "relevance_score",
- "sort_order": "desc",
- "filters": { }
}{- "profile_id": 0,
- "profile_title": "string",
- "total_creators": 0,
- "page": 0,
- "limit": 0,
- "creators": [
- { }
]
}Resolve one brief and materialize its entire match set into a named Vault list — the bridge from 'who should I recruit?' to an automation you can actually run, since automations target Vault lists rather than briefs.
This is a snapshot: the list holds the creators matching at save time and does not track the brief afterwards. Re-save to refresh. Pass include_emails: true to store the email list alongside the handles, which an email automation needs. Optional filters narrow what gets saved without editing the brief.
Every matching creator is saved, not just the first page, so this can take a while on a broad brief. The response reports creators_saved.
Requires read_write scope and an Idempotency-Key header. Returns 404 if the profile does not exist or belongs to another shop. Single-shop only.
| profile_id required | integer (Profile Id) >= 1 Discovery Agent profile ID. |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) |
| list_name required | string (List Name) [ 1 .. 255 ] characters Name for the Vault list. |
Description (string) or Description (null) (Description) Optional list description. | |
| include_emails | boolean (Include Emails) Default: false Also save the creators' email list alongside the handle list. Required if you intend to drive an email automation from it. |
Filters (object) or Filters (null) (Filters) Extra filters narrowing the saved set, same shape as the AI-search filter payload. The profile itself is not modified. |
{- "list_name": "string",
- "description": "string",
- "include_emails": false,
- "filters": { }
}{- "success": true,
- "message": "string",
- "list_name": "string",
- "creators_saved": 0,
- "email_list_saved": true
}The shop's connection matrix — one row per integration Reacher offers it, each with a key, a status and, when connected, the connected_at timestamp.
Covers tiktok_shop_affiliate and tiktok_shop_customer_service (the TikTok Shop apps that back creator outreach and messaging), tiktok_for_business (the ads account behind GMV Max), meta (the Facebook/Instagram account behind Meta Ads rev-share), amazon and shopify (the off-TikTok revenue channels the Halo tracker correlates against), and discord. status is connected, disconnected, or coming_soon where the integration isn't available to this shop's region yet.
Which rows appear is region-dependent: an integration with no presence in the shop's region is omitted rather than returned as disconnected. Use this to explain an empty data surface — Halo with no Amazon revenue and a disconnected amazon row is a setup gap, not missing data.
Connecting an integration is a browser OAuth flow, so the portal's install links are not part of this response. Read-only, single-shop.
{- "shop_id": 0,
- "integrations": [
- {
- "key": "string",
- "status": "string",
- "connected_at": "string"
}
]
}One row per product for the window, from the Seller Center product-performance rollup.
funnel walks impression → click → add-to-cart → order, and channels splits the product's GMV across the seller's own video and live content, creator (affiliate) video and live, the product card, and the shop tab (with its own impression / visitor / conversion counts).
Every total is a SUM over the window's daily rows and every rate is recomputed as SUM(numerator) / SUM(denominator) — never an average of the daily rate columns, which would weight a quiet day the same as a peak one. A rate is null, not 0, when its denominator is zero.
Sort with sort_by (one of ['gmv', 'orders', 'items_sold', 'customers', 'impressions', 'clicks', 'add_to_cart', 'affiliate_gmv', 'shop_tab_gmv', 'refunds']) and sort_dir; page with page / page_size. Defaults to the last 30 days ending yesterday; max range 90 days. Single-shop only — set x-shop-id to a specific shop ID. An empty result is a normal 200 response: Seller Center collection is per-shop and still rolling out.
Start Date (string) or Start Date (null) (Start Date) First day of the window (inclusive). Defaults to 30 days back. | |
End Date (string) or End Date (null) (End Date) Last day of the window (inclusive). Defaults to yesterday. | |
Product Id (string) or Product Id (null) (Product Id) Restrict to a single product. | |
| sort_by | string (Sort By) Default: "gmv" Sort column. One of: ['gmv', 'orders', 'items_sold', 'customers', 'impressions', 'clicks', 'add_to_cart', 'affiliate_gmv', 'shop_tab_gmv', 'refunds']. |
| sort_dir | string (Sort Dir) Default: "desc" Enum: "asc" "desc" Sort direction. |
| page | integer (Page) >= 1 Default: 1 1-based page number. |
| page_size | integer (Page Size) [ 1 .. 200 ] Default: 50 Products per page (max 200). |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "product_id": "string",
- "sort_by": "gmv",
- "sort_dir": "asc",
- "page": 1,
- "page_size": 50
}{- "shop_id": 0,
- "currency_code": "string",
- "start_date": "string",
- "end_date": "string",
- "data": [
- {
- "product_id": "string",
- "product_name": "string",
- "product_status": 0,
- "cover_image_url": "string",
- "days_with_data": 0,
- "sales": {
- "gmv": 0,
- "orders": 0,
- "sku_orders": 0,
- "items_sold": 0,
- "customers": 0,
- "aov": 0,
- "refunds": 0,
- "items_canceled_and_returned": 0
}, - "funnel": {
- "impressions": 0,
- "unique_viewers": 0,
- "clicks": 0,
- "unique_clickers": 0,
- "ctr": 0,
- "add_to_cart": 0,
- "add_to_cart_rate": 0,
- "orders": 0,
- "click_to_order_rate": 0
}, - "channels": {
- "seller": {
- "video_gmv": 0,
- "live_gmv": 0
}, - "affiliate": {
- "gmv": 0,
- "video_gmv": 0,
- "live_gmv": 0
}, - "product_card": {
- "gmv": 0
}, - "shop_tab": {
- "gmv": 0,
- "listing_impressions": 0,
- "page_views": 0,
- "unique_visitors": 0,
- "customers": 0,
- "items_sold": 0,
- "ctr": 0
}
}
}
], - "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0
}
}Window totals for the whole shop from the Seller Center overview, split by the channel the sale came through: video (creator vs seller), live (creator vs seller), and product card (shop tab vs search), plus product impressions / clicks and the derived click-through rate.
comparison carries the comparison-period totals TikTok attaches to each day, summed across the window, and gmv_change_pct derives the swing. TikTok chooses those periods (typically the immediately preceding equal-length window), so treat it as a directional benchmark rather than a window you control.
Related: POST /shop-gmv/timeseries gives the same channel hierarchy day by day, and POST /shop-gmv/summary gives the window totals without the comparison, product-card counts or listing impressions added here. aov is window GMV / window orders; customers is the sum of daily unique-buyer counts, so a buyer who purchased on two days is counted twice. Max range 90 days. Single-shop only — set x-shop-id to a specific shop ID. An empty result is a normal 200 response: Seller Center collection is per-shop and still rolling out.
Start Date (string) or Start Date (null) (Start Date) First day of the window (inclusive). Defaults to 30 days back. | |
End Date (string) or End Date (null) (End Date) Last day of the window (inclusive). Defaults to yesterday. |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24"
}{- "shop_id": 0,
- "currency_code": "string",
- "start_date": "string",
- "end_date": "string",
- "day_count": 0,
- "gmv": 0,
- "orders": 0,
- "items_sold": 0,
- "customers": 0,
- "aov": 0,
- "channels": {
- "video": {
- "gmv": 0,
- "affiliate": 0,
- "seller": 0
}, - "live": {
- "gmv": 0,
- "affiliate": 0,
- "seller": 0
}, - "product_card": {
- "gmv": 0,
- "shop_tab": 0,
- "search": 0,
- "orders": 0,
- "customers": 0
}
}, - "traffic": {
- "product_impressions": 0,
- "product_clicks": 0,
- "product_ctr": 0,
- "shop_tab_listing_impressions": 0
}, - "comparison": {
- "gmv": 0,
- "orders": 0,
- "items_sold": 0,
- "customers": 0,
- "gmv_change_pct": 0
}
}Per-channel performance for the window from the Seller Center shop-tab stats — one entry per channel (shop_tab, search, other) with GMV, impressions, units sold and customers.
baseline is TikTok's previous-period value for the same metrics, summed across the window. industry_median is the typical seller in this shop's industry — a benchmark, not an additive quantity, so it is reported as the most recent day in the window that carried one (as_of) rather than summed or averaged. It is null when TikTok returned no benchmark for the window; the daily collection path does not always include one.
Max range 90 days. Single-shop only — set x-shop-id to a specific shop ID. An empty result is a normal 200 response: Seller Center collection is per-shop and still rolling out.
Start Date (string) or Start Date (null) (Start Date) First day of the window (inclusive). Defaults to 30 days back. | |
End Date (string) or End Date (null) (End Date) Last day of the window (inclusive). Defaults to yesterday. |
{- "start_date": "2019-08-24",
- "end_date": "2019-08-24"
}{- "shop_id": 0,
- "currency_code": "string",
- "start_date": "string",
- "end_date": "string",
- "data": [
- {
- "channel": "string",
- "day_count": 0,
- "gmv": 0,
- "impressions": 0,
- "units_sold": 0,
- "customers": 0,
- "units_per_impression": 0,
- "baseline": {
- "gmv": 0,
- "impressions": 0,
- "units_sold": 0,
- "customers": 0
}, - "industry_median": {
- "as_of": "string",
- "gmv": 0,
- "impressions": 0,
- "units_sold": 0,
- "customers": 0
}
}
]
}The shop's products (ranking_type=product) or videos (ranking_type=video) ranked by how many new customers they brought in, from customers.sc_buyers_ranking. Use it to find the product or piece of content that actually acquires buyers, as opposed to the one that generates the most GMV from buyers you already had.
customer_contribution is the entity's share (0-1) of the shop's new customers over TikTok's trailing period_months window (12 by default).
No date window: this is a snapshot ranking that TikTok recomputes and the collector overwrites wholesale. Collection of this ranking is currently paused upstream, so the response reflects the last snapshot collected for the shop. Single-shop only — set x-shop-id to a specific shop ID. An empty result is a normal 200 response: Seller Center collection is per-shop and still rolling out.
| ranking_type | string (Ranking Type) Default: "product" Enum: "product" "video" Rank products or videos. |
| limit | integer (Limit) [ 1 .. 200 ] Default: 50 Max entries (max 200). |
{- "shop_id": 0,
- "data": [
- {
- "rank": 0,
- "ranking_type": "string",
- "entity_id": "string",
- "entity_name": "string",
- "creator_name": "string",
- "new_customers": 0,
- "customer_contribution": 0,
- "period_months": 0,
- "customer_type": 0
}
]
}The most-viewed videos across the product categories this shop sells in, from customers.sc_top_viewed_videos — competitive intelligence, not the shop's own content (for that use GET /videos).
Each entry carries the video's rank and rank change, the creator's public handle, the view bucket TikTok reports (views_min / views_max — TikTok does not publish an exact count here) and the product the video links to, with its price range and rating.
No date window: the latest collected snapshot is returned, and its range is echoed as start_date / end_date. Collection is currently paused upstream, so the response reflects the last snapshot collected for the shop. Single-shop only — set x-shop-id to a specific shop ID. An empty result is a normal 200 response: Seller Center collection is per-shop and still rolling out.
| limit | integer (Limit) [ 1 .. 200 ] Default: 50 Max videos (max 200). |
{- "shop_id": 0,
- "start_date": "string",
- "end_date": "string",
- "data": [
- {
- "video_id": "string",
- "video_title": "string",
- "thumbnail_url": "string",
- "posted_at_epoch": 0,
- "creator_name": "string",
- "creator_id": "string",
- "rank": 0,
- "rank_change": 0,
- "views_min": 0,
- "views_max": 0,
- "similar_videos_on_site": 0,
- "linked_product": {
- "product_id": "string",
- "product_name": "string",
- "price_min": 0,
- "price_max": 0,
- "rating": 0,
- "review_count": 0
}
}
]
}Daily or weekly (Sunday-start) sales for one shop from TikTok's official Seller Center analytics. Each bucket says how many days it covers and whether it is complete, so a partial current week is never mistaken for a decline. Window: a preset (7d, 30d, 90d, 12m, all) ending at the shop's newest stored day, or explicit start_date/end_date; max 365 days. Single-shop only. An empty points list is a normal 200 for shops the collector does not cover yet.
Preset (string) or Preset (null) (Preset) Relative window ending at the shop's newest settled day: | |
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). | |
| granularity | string (Granularity) Default: "day" Enum: "day" "week"
|
{- "preset": "string",
- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "granularity": "day"
}{- "shop_id": 0,
- "currency_code": "string",
- "start_date": "string",
- "end_date": "string",
- "granularity": "day",
- "week_start": "sunday",
- "latest_available_date": "string",
- "earliest_available_date": "string",
- "is_complete": true,
- "points": [
- {
- "date": "string",
- "date_end": "string",
- "days_expected": 0,
- "days_observed": 0,
- "is_complete": true,
- "currency_code": "string",
- "gmv": 0,
- "video_attributed_gmv": 0,
- "live_attributed_gmv": 0,
- "product_card_gmv": 0,
- "gross_revenue": 0,
- "refunds": 0,
- "orders": 0,
- "sku_orders": 0,
- "items_sold": 0,
- "customers": 0,
- "page_views": 0,
- "visitors": 0,
- "conversion_rate": 0
}
]
}The same window as /shop-performance/timeseries split by sales channel: affiliate (creator) GMV with its video/live parts, the shop's own video and live GMV, product-card GMV, shop-tab GMV, plus clicks, impressions and orders per channel. affiliate_gmv already includes affiliate_video_gmv and affiliate_live_gmv, and shop_tab_gmv overlaps the four channels rather than adding to them: gmv = affiliate + seller_video + seller_live + product_card (+ other_gmv). Single-shop only.
Preset (string) or Preset (null) (Preset) Relative window ending at the shop's newest settled day: | |
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). | |
| granularity | string (Granularity) Default: "day" Enum: "day" "week"
|
{- "preset": "string",
- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "granularity": "day"
}{- "shop_id": 0,
- "currency_code": "string",
- "start_date": "string",
- "end_date": "string",
- "granularity": "day",
- "week_start": "sunday",
- "latest_available_date": "string",
- "earliest_available_date": "string",
- "is_complete": true,
- "points": [
- {
- "date": "string",
- "date_end": "string",
- "days_expected": 0,
- "days_observed": 0,
- "is_complete": true,
- "currency_code": "string",
- "gmv": 0,
- "refunds": 0,
- "other_gmv": 0,
- "orders": 0,
- "items_sold": 0,
- "product_impressions": 0,
- "product_clicks": 0,
- "add_cart_count": 0,
- "product_count": 0,
- "affiliate_gmv": 0,
- "affiliate_orders": 0,
- "affiliate_clicks": 0,
- "affiliate_impressions": 0,
- "affiliate_video_gmv": 0,
- "affiliate_video_clicks": 0,
- "affiliate_video_impressions": 0,
- "affiliate_live_gmv": 0,
- "affiliate_live_clicks": 0,
- "affiliate_live_impressions": 0,
- "seller_video_gmv": 0,
- "seller_video_orders": 0,
- "seller_video_clicks": 0,
- "seller_video_impressions": 0,
- "seller_live_gmv": 0,
- "seller_live_orders": 0,
- "seller_live_clicks": 0,
- "seller_live_impressions": 0,
- "product_card_gmv": 0,
- "product_card_orders": 0,
- "product_card_clicks": 0,
- "product_card_impressions": 0,
- "shop_tab_gmv": 0,
- "shop_tab_sold_items": 0,
- "shop_tab_clicks": 0,
- "shop_tab_impressions": 0,
- "pages_fetched": 0,
- "is_walk_complete": true
}
]
}One shop's products summed over the window from TikTok's daily product list, ranked by sort (gmv, orders, product_clicks or add_cart_count), limit products per page; product_total says how many products the window held in all. Products the catalogue knows are deactivated or rejected are left out unless include_inactive is true; each row carries listing_status (live, out_of_stock, seller_deactivated, platform_deactivated, rejected, or null when unknown) and the listed price_min/price_max in price_currency; listing_data_available is false when the catalogue could not be read (nothing excluded, listings null). Per product: GMV, refunds, orders, units, impressions, clicks, add-to-cart, and GMV plus orders for each channel — creators (affiliate), the shop's own videos and lives, product page — with shop_tab_gmv as an overlapping view and other_gmv the remainder: gmv = affiliate + seller_video + seller_live + product_card + other. Same window grammar as /shop-performance/timeseries. Single-shop only; a product's days_observed says on how many days of the window TikTok listed it.
Preset (string) or Preset (null) (Preset) Relative window ending at the shop's newest settled day: | |
Start Date (string) or Start Date (null) (Start Date) Inclusive window start (YYYY-MM-DD). | |
End Date (string) or End Date (null) (End Date) Inclusive window end (YYYY-MM-DD). | |
| limit | integer (Limit) [ 1 .. 100 ] Default: 20 Products per page. |
| page | integer (Page) [ 1 .. 10000 ] Default: 1 1-based page over the ranked list. |
| sort | string (Sort) Default: "gmv" Enum: "gmv" "orders" "product_clicks" "add_cart_count" Rank by this window total, descending. |
| include_inactive | boolean (Include Inactive) Default: false Also list products the catalogue knows are deactivated or rejected. Off by default; out-of-stock products are always listed. |
{- "preset": "string",
- "start_date": "2019-08-24",
- "end_date": "2019-08-24",
- "limit": 20,
- "page": 1,
- "sort": "gmv",
- "include_inactive": false
}{- "shop_id": 0,
- "currency_code": "string",
- "start_date": "string",
- "end_date": "string",
- "latest_available_date": "string",
- "earliest_available_date": "string",
- "sort": "gmv",
- "page": 1,
- "product_total": 0,
- "listing_data_available": true,
- "products": [
- {
- "product_id": "string",
- "product_name": "string",
- "image_url": "string",
- "days_observed": 0,
- "listing_status": "live",
- "price_min": 0,
- "price_max": 0,
- "price_currency": "string",
- "currency_code": "string",
- "gmv": 0,
- "refunds": 0,
- "orders": 0,
- "items_sold": 0,
- "product_impressions": 0,
- "product_clicks": 0,
- "add_cart_count": 0,
- "affiliate_gmv": 0,
- "affiliate_orders": 0,
- "seller_video_gmv": 0,
- "seller_video_orders": 0,
- "seller_live_gmv": 0,
- "seller_live_orders": 0,
- "product_card_gmv": 0,
- "product_card_orders": 0,
- "shop_tab_gmv": 0,
- "other_gmv": 0
}
]
}Stop one or more of a campaign's videos from counting toward its results — leaderboard standings, stats and payout eligibility are re-aggregated once for the whole batch before the response returns, so send the entire selection in ONE call rather than one call per video.
Every video id must already belong to this campaign (unknown ids → 404). At least one reasons code is required and a note is mandatory when the reason is other, because the reason reaches the creator. Rejected with 409 when the campaign's results are already final, or when it is a LIVE campaign (scored from live sessions, which can't be excluded individually).
Requires read_write scope and an Idempotency-Key; X-Dry-Run: true echoes the intended change without writing.
| campaign_id required | integer (Campaign Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| video_ids required | Array of strings (Video Ids) [ 1 .. 5000 ] items |
| reasons required | Array of strings (Reasons) non-empty Items Enum: "guidelines" "off_brand" "wrong_product" "duplicate" "other" |
| note | string (Note) <= 500 characters Default: "" |
{- "video_ids": [
- "string"
], - "reasons": [
- "guidelines"
], - "note": ""
}{- "success": true,
- "code": "string",
- "data": {
- "excluded_video_ids": [
- "string"
], - "already_excluded_video_ids": [
- "string"
]
}
}Count one or more previously excluded videos toward the campaign again — the inverse of the exclude call, batched and re-aggregated the same way. Ids that were never excluded are simply no-ops.
Same 409 guards apply: a campaign whose results are final, or a LIVE campaign, cannot be changed.
Requires read_write scope and an Idempotency-Key; X-Dry-Run: true echoes the intended change without writing.
| campaign_id required | integer (Campaign Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| video_ids required | Array of strings (Video Ids) [ 1 .. 5000 ] items |
{- "video_ids": [
- "string"
]
}{- "success": true,
- "code": "string",
- "data": {
- "restored_video_ids": [
- "string"
], - "not_excluded_video_ids": [
- "string"
]
}
}Dry run for a batch of video ids: resolves which campaigns each id actually appears in and returns the summary — matched, unknown, and skipped-because-ended — WITHOUT writing anything.
scope defaults to campaign (act only on this campaign, what a brand expects from a button on one campaign's Content tab). scope: shop is the opt-in that also resolves every OTHER campaign the video appears in — far more destructive, so it is never implied. Always preview a shop-scoped batch before applying it.
Read-only, but gated on read_write scope: it is the first step of the bulk-exclusion write flow and reveals exactly what that flow would change. No Idempotency-Key needed (nothing is written).
| campaign_id required | integer (Campaign Id) >= 1 |
| video_ids required | Array of strings (Video Ids) [ 1 .. 1000 ] items |
| scope | string (ExclusionScope) Default: "campaign" Enum: "campaign" "shop" How far an uploaded file reaches.
|
{- "video_ids": [
- "string"
], - "scope": "campaign"
}nullApplies a batch of video ids across every campaign the chosen scope resolves to. Returns a job_id IMMEDIATELY — a wide sweep costs one full re-aggregation per campaign and would outlive the request otherwise. Poll GET /campaigns/{campaign_id}/content/exclusions/jobs/{job_id} for progress.
The job RE-RESOLVES the targets rather than trusting a preview: minutes can pass between preview and apply, and in that window a creator can be removed or another manager can exclude the same video.
Same scope semantics and reason/note rules as the preview and single-exclude calls. Requires read_write scope and an Idempotency-Key; X-Dry-Run: true echoes the intended job without enqueuing it.
| campaign_id required | integer (Campaign Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| video_ids required | Array of strings (Video Ids) [ 1 .. 1000 ] items |
| reasons required | Array of strings (Reasons) non-empty Items Enum: "guidelines" "off_brand" "wrong_product" "duplicate" "other" |
| note | string (Note) <= 500 characters Default: "" |
| scope | string (ExclusionScope) Default: "campaign" Enum: "campaign" "shop" How far an uploaded file reaches.
|
Source File (string) or Source File (null) (Source File) |
{- "video_ids": [
- "string"
], - "reasons": [
- "guidelines"
], - "note": "",
- "scope": "campaign",
- "source_file": "string"
}nullThe bulk-exclusion job still running for this campaign, if any — so a caller that lost the job_id (a reload, a crashed worker) can rejoin it instead of re-uploading. data is null when nothing is running. Read-only.
| campaign_id required | integer (Campaign Id) >= 1 |
nullProgress for one bulk-exclusion job: status, campaigns processed vs total, whole-percent progress, exclusions applied, per-campaign errors, and is_terminal. Poll until is_terminal is true. Read-only.
| campaign_id required | integer (Campaign Id) >= 1 |
| job_id required | string (Job Id) [ 1 .. 64 ] characters |
nullCreates a micro-challenge ('weekend blitz') under a parent Leaderboard (contest) campaign — a short scoring window with its own pricing and its own board.
Two shapes, discriminated by schedule.mode:
one_off — an explicit start_at/end_at window (shop-local); materializes an instance immediately.recurring — a weekly window (start_dow/start_time → end_dow/end_time, day 0 = Monday, shop-local); creates a TEMPLATE that the daily materializer turns into instances. Use POST /micro-challenges/preview-window to see the next window a draft recurring schedule would produce.Rejected when the parent isn't a running contest campaign, when it is itself a micro-challenge (no nesting), or when the parent already has the maximum number of ACTIVE templates (409).
Requires read_write scope and an Idempotency-Key; X-Dry-Run: true validates the shop/campaign and echoes the payload without creating anything.
| campaign_id required | integer (Campaign Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| name required | string (Name) [ 1 .. 255 ] characters |
required | any (Schedule) |
required | object (LeaderboardPricing) Pricing configuration for leaderboard campaigns. Extra keys are IGNORED (the codebase default, matching every sibling
pricing class): the brand FE sends UI-only toggles like
|
| excluded_creator_ids | Array of integers (Excluded Creator Ids) <= 5000 items |
{- "name": "string",
- "schedule": {
- "mode": "one_off",
- "start_at": "2019-08-24T14:15:22Z",
- "end_at": "2019-08-24T14:15:22Z"
}, - "pricing": {
- "campaign_type": "leaderboard",
- "leaderboard_ranks": [
- {
- "rank": 1,
- "reward": 0
}
], - "reward_type": "cash",
- "metric_type": "gmv",
- "scoring_mode": "single",
- "weighted_metrics": [
- {
- "metric_type": "gmv",
- "weight": 100
}
], - "minimum_gmv_required": 0,
- "minimum_views_required": 0,
- "minimum_videos_required": 0,
- "minimum_live_gmv_required": 0,
- "minimum_sessions_required": 0
}, - "excluded_creator_ids": [
- 0
]
}nullThe campaign's recurring micro-challenge TEMPLATES (with their schedule, pricing and active/paused status) alongside the INSTANCES already materialized from them plus any one-off windows. Read-only.
| campaign_id required | integer (Campaign Id) >= 1 |
nullPartial update of a RECURRING template — name, window (start_dow/start_time/end_dow/end_time, or an equivalent nested schedule object), pricing, excluded creators, or status: paused to stop it producing new instances.
Affects FUTURE instances only: windows already materialized are never rewritten. When a not-yet-started instance exists for the template, it is returned as already_scheduled_instance so the caller can see what the edit did not touch. Ownership is enforced on the full (template, campaign, shop) triple — a template id from another campaign 404s.
Requires read_write scope and an Idempotency-Key; X-Dry-Run: true echoes the patch without applying it.
| campaign_id required | integer (Campaign Id) >= 1 |
| template_id required | integer (Template Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
Name (string) or Name (null) (Name) | |
Schedule (any) or Schedule (null) (Schedule) | |
Start Dow (integer) or Start Dow (null) (Start Dow) | |
Start Time (string) or Start Time (null) (Start Time) | |
End Dow (integer) or End Dow (null) (End Dow) | |
End Time (string) or End Time (null) (End Time) | |
LeaderboardPricing (object) or null | |
Array of Excluded Creator Ids (integers) or Excluded Creator Ids (null) (Excluded Creator Ids) | |
Status (string) or Status (null) (Status) |
{- "name": "string",
- "schedule": {
- "mode": "one_off",
- "start_at": "2019-08-24T14:15:22Z",
- "end_at": "2019-08-24T14:15:22Z"
}, - "start_dow": 6,
- "start_time": "14:15:22Z",
- "end_dow": 6,
- "end_time": "14:15:22Z",
- "pricing": {
- "campaign_type": "leaderboard",
- "leaderboard_ranks": [
- {
- "rank": 1,
- "reward": 0
}
], - "reward_type": "cash",
- "metric_type": "gmv",
- "scoring_mode": "single",
- "weighted_metrics": [
- {
- "metric_type": "gmv",
- "weight": 100
}
], - "minimum_gmv_required": 0,
- "minimum_views_required": 0,
- "minimum_videos_required": 0,
- "minimum_live_gmv_required": 0,
- "minimum_sessions_required": 0
}, - "excluded_creator_ids": [
- 0
], - "status": "active"
}nullSoft-deletes a recurring template so it stops materializing new windows. Instances it ALREADY produced are untouched — their standings and payouts stand. Ownership is enforced on the full (template, campaign, shop) triple.
Requires read_write scope and an Idempotency-Key; X-Dry-Run: true reports what would be deleted without deleting it.
| campaign_id required | integer (Campaign Id) >= 1 |
| template_id required | integer (Template Id) >= 1 |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
nullResolves a draft weekly schedule (start_dow/start_time → end_dow/end_time, day 0 = Monday) against the shop's timezone and returns the next concrete window it would produce — so a caller can confirm the window before creating a template.
Shop-scoped, NOT campaign-nested: the create flow has no campaign yet when the schedule is being drafted. The shop comes from x-shop-id; a shop_id in the body is ignored.
Writes nothing, but gated on read_write scope as the first step of the micro-challenge create flow. No Idempotency-Key needed.
Shop Id (integer) or Shop Id (null) (Shop Id) | |
| start_dow required | integer (Start Dow) [ 0 .. 6 ] |
| start_time required | string <time> (Start Time) |
| end_dow required | integer (End Dow) [ 0 .. 6 ] |
| end_time required | string <time> (End Time) |
{- "shop_id": 0,
- "start_dow": 6,
- "start_time": "14:15:22Z",
- "end_dow": 6,
- "end_time": "14:15:22Z"
}nullAnalyze this product's already-analyzed TikTok videos and generate a creative brief — content angles, unique selling points, filming recommendations, dos/don'ts, and the top inspiration videos — then save it as a drafted brief and return it.
Each call is a fresh generation that spends model tokens and creates a new brief row, so Idempotency-Key is required: a retried request replays the first result instead of paying for and storing a duplicate. Send X-Dry-Run: true to validate the product without generating.
Returns 404 when the product isn't in this shop, and 422 when the product has no analyzed videos to build a brief from — run video analysis first.
| product_id required | string (Product Id) |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
{- "data": { },
- "dry_run": false
}Every creative brief in this shop, newest revision per brief, with the product's name and cover image for display. Briefs whose product has no analyzed videos or no catalog imagery are omitted — they cannot render.
last_opened_at is the last time the brief was opened in the portal, or null. Read-only.
nullThe public URL of this product's published creative brief — the link you share with creators. Returns {"url": null} when the product has no published brief (a drafted or archived brief is not shareable). Read-only.
| product_id required | string (Product Id) <= 100 characters |
{- "url": "string"
}One creative brief in full: the generated document (brief_content) plus its flattened sections, the inspiration videos with their order counts, and the shop's brand colors and logo. Image URLs are re-signed on every read, so use them promptly rather than storing them. Read-only.
| brief_id required | integer (Brief Id) |
nullReplace this brief's editable content — name, description, angles, USPs, filming recommendations, dos/don'ts, inspiration videos — and its status. Set status: published to make it shareable (its URL is then available from GET /creative-briefs/published-url), or archived to retire it.
This is a replace: the stored document is rebuilt from the body, so send the whole brief back with your edits, not just changed fields.
Two things happen automatically on save: inspiration videos missing key_points get them AI-generated from that video's analysis, and videos missing an orders count have it filled from our attribution data. Idempotency-Key is optional here (a replace is naturally repeatable); X-Dry-Run: true validates without writing.
| brief_id required | integer (Brief Id) |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
Brief Name (string) or Brief Name (null) (Brief Name) | |
Product Description (string) or Product Description (null) (Product Description) | |
| when_to_use | string (When To Use) Default: "" |
| image_urls | Array of strings (Image Urls) <= 50 items |
| content_angles | Array of any (Content Angles) <= 50 items |
| unique_selling_points | Array of any (Unique Selling Points) <= 50 items |
object (Filming Recommendations) | |
object (Dos And Donts) | |
| content_inspiration | Array of any (Content Inspiration) <= 50 items Inspiration videos. Entries missing |
| status | string (Status) ^(drafted|published|archived)$ Default: "drafted" |
{- "brief_name": "string",
- "product_description": "string",
- "when_to_use": "",
- "image_urls": [
- "string"
], - "content_angles": [
- null
], - "unique_selling_points": [
- null
], - "filming_recommendations": { },
- "dos_and_donts": { },
- "content_inspiration": [
- null
], - "status": "drafted"
}{- "data": { },
- "dry_run": false
}Generate the copy for a single-image creator brief from up to 3 of your products (index 0 is the hero): the kicker, signature and winning hooks, social proof, benefits, content ideas, and shop tagline.
This returns content without saving it — pass the result to POST /image-briefs to keep it as a draft. The portal streams this generation token by token; here it is collapsed into one response.
Each call spends model tokens, so Idempotency-Key is required and retries replay the first result rather than regenerating. Generation is non-deterministic — the same products give different copy each time.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| product_ids required | Array of strings (Product Ids) Products to build the brief from; index 0 is the hero. At most 3. |
{- "product_ids": [
- "string"
]
}{- "data": {
- "kicker": "",
- "signatureHooks": [
- "string"
], - "winningHooks": [
- {
- "text": "string",
- "lead": true
}
], - "socialProof": [
- "string"
], - "benefits": [
- {
- "term": "string",
- "detail": "string"
}
], - "contentIdeas": [
- {
- "title": "string",
- "detail": "string"
}
], - "shopTagline": ""
}, - "dry_run": false
}The product details an image brief renders and generates from — name, brand, price, image, description, top-performing hooks, and your brand voice — for up to 3 products (first is the hero). Use it to preview the poster header before generating. Read-only.
| product_ids required | string (Product Ids) Comma-separated product IDs; the first is the hero. |
{- "products": [
- {
- "id": "string",
- "name": "string",
- "brand": "",
- "brandHandle": "",
- "price": "",
- "unit": "",
- "image": "",
- "description": "",
- "badges": [
- "string"
], - "topHooks": [
- "string"
], - "brandVoice": ""
}
]
}The adjectives describing how your brand sounds (e.g. playful, clinical, premium). They are fed to every image-brief generation, so they shape the copy's tone. Read-only.
{- "shop_id": 0,
- "voice_descriptors": [
- "string"
], - "brand_voice": ""
}Replace the descriptors that steer the tone of generated image briefs. This is a replace, not an append — send the full list.
Requires an existing brand profile for the shop; 404 until brand analysis has run. Affects future generations only; existing briefs keep their saved copy.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| voice_descriptors | Array of strings (Voice Descriptors) |
{- "voice_descriptors": [
- "string"
]
}{- "data": {
- "shop_id": 0,
- "voice_descriptors": [
- "string"
], - "brand_voice": ""
}, - "dry_run": false
}Scrape the brand's website and extract a brand profile — name, voice descriptors, target audience, categories — and store it for the shop. This is what PUT /image-briefs/brand-voice depends on: a shop that has never had brand analysis run 404s there until this endpoint has created the profile. After it runs, brand voice is populated (and can be overridden with the PUT).
It scrapes the site and runs an LLM, so it is a write: it needs read_write scope, counts against the write rate limit, and the shop's existing profile is returned as-is unless refresh: true (a shop has one profile). A required Idempotency-Key makes a retry replay rather than re-scrape; X-Dry-Run: true validates without scraping. Single shop only.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| website_url required | string (Website Url) [ 4 .. 2048 ] characters The brand's public website URL to scrape and analyse (e.g. https://www.thebean.com). |
| refresh | boolean (Refresh) Default: false Re-run the analysis even if a profile already exists for this URL. |
{- "website_url": "string",
- "refresh": false
}{- "data": {
- "website_url": "string",
- "brand_name": "string",
- "voice_descriptors": [
- "string"
], - "target_audience": "string",
- "extracted_categories": [
- "string"
], - "extraction_model": "string",
- "extracted_at": "string",
- "source": "string"
}, - "dry_run": false
}Your saved image-brief drafts, newest first, each with the poster identity (name, brand, price, image) of its products. Filter by name or kicker with search. Read-only.
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 20 |
Search (string) or Search (null) (Search) Case-insensitive name/kicker filter. |
{- "briefs": [
- {
- "id": 0,
- "shop_id": 0,
- "product_ids": [
- null
], - "template_id": "string",
- "accent_color": "string",
- "main_header_background": "string",
- "name": "string",
- "content": {
- "kicker": "",
- "signatureHooks": [
- "string"
], - "winningHooks": [
- {
- "text": "string",
- "lead": true
}
], - "socialProof": [
- "string"
], - "benefits": [
- {
- "term": "string",
- "detail": "string"
}
], - "contentIdeas": [
- {
- "title": "string",
- "detail": "string"
}
], - "shopTagline": ""
}, - "image_overrides": { },
- "status": "string",
- "created_by_email": "string",
- "created_at": "string",
- "updated_at": "string",
- "products": [
- {
- "id": "string",
- "name": "",
- "brand": "",
- "price": "",
- "image": ""
}
]
}
], - "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0
}
}Save an image brief as a draft — typically the content returned by POST /image-briefs/generate, but any content is accepted. Up to 3 products; index 0 is the hero. Omit name and the brief is named after its headline.
Idempotency-Key is required so a retry doesn't leave you with duplicate drafts. X-Dry-Run: true validates without saving.
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
| product_ids | Array of strings (Product Ids) |
| template_id | string (Template Id) Default: "editorial-dark" |
Accent Color (string) or Accent Color (null) (Accent Color) | |
Main Header Background (string) or Main Header Background (null) (Main Header Background) | |
Name (string) or Name (null) (Name) | |
object (BriefContent) | |
object (Image Overrides) |
{- "product_ids": [
- "string"
], - "template_id": "editorial-dark",
- "accent_color": "string",
- "main_header_background": "string",
- "name": "string",
- "content": {
- "kicker": "",
- "signatureHooks": [
- "string"
], - "winningHooks": [
- {
- "text": "string",
- "lead": true
}
], - "socialProof": [
- "string"
], - "benefits": [
- {
- "term": "string",
- "detail": "string"
}
], - "contentIdeas": [
- {
- "title": "string",
- "detail": "string"
}
], - "shopTagline": ""
}, - "image_overrides": { }
}{- "data": {
- "id": 0,
- "shop_id": 0,
- "product_ids": [
- null
], - "template_id": "string",
- "accent_color": "string",
- "main_header_background": "string",
- "name": "string",
- "content": {
- "kicker": "",
- "signatureHooks": [
- "string"
], - "winningHooks": [
- {
- "text": "string",
- "lead": true
}
], - "socialProof": [
- "string"
], - "benefits": [
- {
- "term": "string",
- "detail": "string"
}
], - "contentIdeas": [
- {
- "title": "string",
- "detail": "string"
}
], - "shopTagline": ""
}, - "image_overrides": { },
- "status": "string",
- "created_by_email": "string",
- "created_at": "string",
- "updated_at": "string",
- "products": [
- {
- "id": "string",
- "name": "",
- "brand": "",
- "price": "",
- "image": ""
}
]
}, - "dry_run": false
}One saved image brief with its full content and layout settings. Read-only.
| brief_id required | integer (Brief Id) |
{- "id": 0,
- "shop_id": 0,
- "product_ids": [
- null
], - "template_id": "string",
- "accent_color": "string",
- "main_header_background": "string",
- "name": "string",
- "content": {
- "kicker": "",
- "signatureHooks": [
- "string"
], - "winningHooks": [
- {
- "text": "string",
- "lead": true
}
], - "socialProof": [
- "string"
], - "benefits": [
- {
- "term": "string",
- "detail": "string"
}
], - "contentIdeas": [
- {
- "title": "string",
- "detail": "string"
}
], - "shopTagline": ""
}, - "image_overrides": { },
- "status": "string",
- "created_by_email": "string",
- "created_at": "string",
- "updated_at": "string",
- "products": [
- {
- "id": "string",
- "name": "",
- "brand": "",
- "price": "",
- "image": ""
}
]
}Change a saved image brief. Only the fields you send are updated, so you can patch a single value; sending an explicit null clears a nullable field. Up to 3 products.
Idempotency-Key is optional (a patch is naturally repeatable); X-Dry-Run: true validates without writing.
| brief_id required | integer (Brief Id) |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
Array of Product Ids (strings) or Product Ids (null) (Product Ids) | |
Template Id (string) or Template Id (null) (Template Id) | |
Accent Color (string) or Accent Color (null) (Accent Color) | |
Main Header Background (string) or Main Header Background (null) (Main Header Background) | |
Name (string) or Name (null) (Name) | |
BriefContent (object) or null | |
Image Overrides (object) or Image Overrides (null) (Image Overrides) | |
Status (string) or Status (null) (Status) New lifecycle state, or omit to leave unchanged. |
{- "product_ids": [
- "string"
], - "template_id": "string",
- "accent_color": "string",
- "main_header_background": "string",
- "name": "string",
- "content": {
- "kicker": "",
- "signatureHooks": [
- "string"
], - "winningHooks": [
- {
- "text": "string",
- "lead": true
}
], - "socialProof": [
- "string"
], - "benefits": [
- {
- "term": "string",
- "detail": "string"
}
], - "contentIdeas": [
- {
- "title": "string",
- "detail": "string"
}
], - "shopTagline": ""
}, - "image_overrides": { },
- "status": "draft"
}{- "data": {
- "id": 0,
- "shop_id": 0,
- "product_ids": [
- null
], - "template_id": "string",
- "accent_color": "string",
- "main_header_background": "string",
- "name": "string",
- "content": {
- "kicker": "",
- "signatureHooks": [
- "string"
], - "winningHooks": [
- {
- "text": "string",
- "lead": true
}
], - "socialProof": [
- "string"
], - "benefits": [
- {
- "term": "string",
- "detail": "string"
}
], - "contentIdeas": [
- {
- "title": "string",
- "detail": "string"
}
], - "shopTagline": ""
}, - "image_overrides": { },
- "status": "string",
- "created_by_email": "string",
- "created_at": "string",
- "updated_at": "string",
- "products": [
- {
- "id": "string",
- "name": "",
- "brand": "",
- "price": "",
- "image": ""
}
]
}, - "dry_run": false
}Permanently delete a saved image brief. This is not reversible — the draft and its content are gone. Deleting an already-deleted brief returns 404.
Idempotency-Key is required; X-Dry-Run: true reports what would be deleted without deleting it.
| brief_id required | integer (Brief Id) |
Idempotency-Key (string) or Idempotency-Key (null) (Idempotency-Key) | |
X-Dry-Run (string) or X-Dry-Run (null) (X-Dry-Run) |
{- "data": {
- "brief_id": 0,
- "deleted": true
}, - "dry_run": false
}Every contract the shop has generated, newest first. Each row carries the campaign and creator it was sent to, the full terms snapshot the document was rendered from, the signing status and both signature timestamps.
Filter by status (draft/sent/active/expired/voided/delivery_failed), by creator_id, by campaign_id, or by expiring_within_days to find active contracts about to lapse. status=expired and expiring_within_days are mutually exclusive — expiry is evaluated lazily against today's date, so asking for both is contradictory.
Cursor-paginated: pass the next_cursor from the previous response to get the next page. A null next_cursor means the last page. Single-shop only.
Status (string) or Status (null) (Status) Restrict to one signing status. | |
Creator Id (integer) or Creator Id (null) (Creator Id) Restrict to contracts sent to one creator. | |
Campaign Id (integer) or Campaign Id (null) (Campaign Id) Restrict to contracts generated for one campaign. | |
Expiring Within Days (integer) or Expiring Within Days (null) (Expiring Within Days) Only active contracts expiring within this many days. | |
Cursor (string) or Cursor (null) (Cursor) Opaque cursor from | |
| limit | integer (Limit) [ 1 .. 200 ] Default: 50 Rows per page (max 200). |
{- "data": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "batch_id": "4da22c97-b7d5-4e31-8c3a-03870ebc7b20",
- "campaign_id": 0,
- "campaign_name": "string",
- "creator_id": 0,
- "creator_name": "string",
- "creator_handle": "string",
- "contract_title": "string",
- "template_source": "standard",
- "terms": {
- "campaign_type": "retainer",
- "brand_name": "string",
- "brand_representative_name": "string",
- "deliverables": 1,
- "posting_platform": "TikTok",
- "posting_requirements": "",
- "posting_start_date": "2019-08-24",
- "effective_date": "2019-08-24",
- "expiry_date": "2019-08-24",
- "products": [
- {
- "product_id": "string",
- "product_name": "string"
}
], - "all_products": false,
- "require_video_link_email": false,
- "video_link_email": "string",
- "commission_pct": 100,
- "expenses_reimbursable": false,
- "revision_rounds": 0,
- "include_usage_rights": true,
- "usage_rights_months": 3,
- "usage_rights_duration_days": 90,
- "usage_rights_scope": "organic_only",
- "usage_exclusivity_enabled": false,
- "usage_exclusivity_months": 3,
- "agreement_clause": "string",
- "termination_notice_days": 30,
- "governing_law_jurisdiction": "New York",
- "reward": {
- "kind": "retainer",
- "mode": "fixed",
- "payment_per_post": 0,
- "tiers": [
- {
- "name": "string",
- "retainer": 0,
- "gmv_threshold": 0
}
], - "gmv_bonus_tiers": [
- {
- "gmv_threshold": 0,
- "bonus_amount": 0
}
], - "recurring": false,
- "payment_schedule": "one_time",
- "payment_due_days": 30,
- "minimum_gmv_floor": 0
}
}, - "status": "draft",
- "brand_signed_at": "2019-08-24T14:15:22Z",
- "creator_signed_at": "2019-08-24T14:15:22Z",
- "effective_date": "2019-08-24",
- "expiry_date": "2019-08-24",
- "last_error": "string"
}
], - "next_cursor": "string"
}The shop's saved contract-clause templates — the reusable clause documents a contract body can be built from. Returns each template's name, clause count and timestamps; the clause bodies themselves are portal-only.
Cursor-paginated: pass the next_cursor from the previous response to get the next page. A null next_cursor means the last page. Single-shop only.
Cursor (string) or Cursor (null) (Cursor) Opaque cursor from | |
| limit | integer (Limit) [ 1 .. 200 ] Default: 50 Templates per page (max 200). |
{- "data": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "clause_count": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "next_cursor": "string"
}Progress for one batch of contracts: total_count / sent_count / failed_count, every envelope in the batch, and a failures list naming the creators whose delivery failed and why. Poll this after a batch is sent from the portal to watch it drain.
404 when the batch does not belong to this shop. Single-shop only.
| batch_id required | string <uuid> (Batch Id) Batch id (UUID). |
{- "batch_id": "4da22c97-b7d5-4e31-8c3a-03870ebc7b20",
- "status": "queued",
- "total_count": 0,
- "sent_count": 0,
- "failed_count": 0,
- "envelopes": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "batch_id": "4da22c97-b7d5-4e31-8c3a-03870ebc7b20",
- "campaign_id": 0,
- "campaign_name": "string",
- "creator_id": 0,
- "creator_name": "string",
- "creator_handle": "string",
- "contract_title": "string",
- "template_source": "standard",
- "terms": {
- "campaign_type": "retainer",
- "brand_name": "string",
- "brand_representative_name": "string",
- "deliverables": 1,
- "posting_platform": "TikTok",
- "posting_requirements": "",
- "posting_start_date": "2019-08-24",
- "effective_date": "2019-08-24",
- "expiry_date": "2019-08-24",
- "products": [
- {
- "product_id": "string",
- "product_name": "string"
}
], - "all_products": false,
- "require_video_link_email": false,
- "video_link_email": "string",
- "commission_pct": 100,
- "expenses_reimbursable": false,
- "revision_rounds": 0,
- "include_usage_rights": true,
- "usage_rights_months": 3,
- "usage_rights_duration_days": 90,
- "usage_rights_scope": "organic_only",
- "usage_exclusivity_enabled": false,
- "usage_exclusivity_months": 3,
- "agreement_clause": "string",
- "termination_notice_days": 30,
- "governing_law_jurisdiction": "New York",
- "reward": {
- "kind": "retainer",
- "mode": "fixed",
- "payment_per_post": 0,
- "tiers": [
- {
- "name": "string",
- "retainer": 0,
- "gmv_threshold": 0
}
], - "gmv_bonus_tiers": [
- {
- "gmv_threshold": 0,
- "bonus_amount": 0
}
], - "recurring": false,
- "payment_schedule": "one_time",
- "payment_due_days": 30,
- "minimum_gmv_floor": 0
}
}, - "status": "draft",
- "brand_signed_at": "2019-08-24T14:15:22Z",
- "creator_signed_at": "2019-08-24T14:15:22Z",
- "effective_date": "2019-08-24",
- "expiry_date": "2019-08-24",
- "last_error": "string"
}
], - "failures": [
- {
- "creator_id": 0,
- "error_code": "string",
- "error_message": "string"
}
]
}Every contract generated for one campaign, newest first — the campaign-scoped view of GET /contracts. Same row shape.
404 when the campaign does not belong to this shop.
Cursor-paginated: pass the next_cursor from the previous response to get the next page. A null next_cursor means the last page. Single-shop only.
| campaign_id required | integer (Campaign Id) >= 1 Campaign id from GET /campaigns. |
Cursor (string) or Cursor (null) (Cursor) Opaque cursor from | |
| limit | integer (Limit) [ 1 .. 200 ] Default: 50 Rows per page (max 200). |
{- "data": [
- {
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "batch_id": "4da22c97-b7d5-4e31-8c3a-03870ebc7b20",
- "campaign_id": 0,
- "campaign_name": "string",
- "creator_id": 0,
- "creator_name": "string",
- "creator_handle": "string",
- "contract_title": "string",
- "template_source": "standard",
- "terms": {
- "campaign_type": "retainer",
- "brand_name": "string",
- "brand_representative_name": "string",
- "deliverables": 1,
- "posting_platform": "TikTok",
- "posting_requirements": "",
- "posting_start_date": "2019-08-24",
- "effective_date": "2019-08-24",
- "expiry_date": "2019-08-24",
- "products": [
- {
- "product_id": "string",
- "product_name": "string"
}
], - "all_products": false,
- "require_video_link_email": false,
- "video_link_email": "string",
- "commission_pct": 100,
- "expenses_reimbursable": false,
- "revision_rounds": 0,
- "include_usage_rights": true,
- "usage_rights_months": 3,
- "usage_rights_duration_days": 90,
- "usage_rights_scope": "organic_only",
- "usage_exclusivity_enabled": false,
- "usage_exclusivity_months": 3,
- "agreement_clause": "string",
- "termination_notice_days": 30,
- "governing_law_jurisdiction": "New York",
- "reward": {
- "kind": "retainer",
- "mode": "fixed",
- "payment_per_post": 0,
- "tiers": [
- {
- "name": "string",
- "retainer": 0,
- "gmv_threshold": 0
}
], - "gmv_bonus_tiers": [
- {
- "gmv_threshold": 0,
- "bonus_amount": 0
}
], - "recurring": false,
- "payment_schedule": "one_time",
- "payment_due_days": 30,
- "minimum_gmv_floor": 0
}
}, - "status": "draft",
- "brand_signed_at": "2019-08-24T14:15:22Z",
- "creator_signed_at": "2019-08-24T14:15:22Z",
- "effective_date": "2019-08-24",
- "expiry_date": "2019-08-24",
- "last_error": "string"
}
], - "next_cursor": "string"
}One contract envelope: the campaign and creator it belongs to, the frozen terms the document was rendered from (deliverables, reward structure, usage rights, dates), the signing status, and brand_signed_at / creator_signed_at — null until that party signs. last_error carries the delivery failure reason when status is delivery_failed.
404 when the contract does not belong to this shop. Single-shop only.
| contract_id required | string <uuid> (Contract Id) Contract id (UUID). |
{- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "batch_id": "4da22c97-b7d5-4e31-8c3a-03870ebc7b20",
- "campaign_id": 0,
- "campaign_name": "string",
- "creator_id": 0,
- "creator_name": "string",
- "creator_handle": "string",
- "contract_title": "string",
- "template_source": "standard",
- "terms": {
- "campaign_type": "retainer",
- "brand_name": "string",
- "brand_representative_name": "string",
- "deliverables": 1,
- "posting_platform": "TikTok",
- "posting_requirements": "",
- "posting_start_date": "2019-08-24",
- "effective_date": "2019-08-24",
- "expiry_date": "2019-08-24",
- "products": [
- {
- "product_id": "string",
- "product_name": "string"
}
], - "all_products": false,
- "require_video_link_email": false,
- "video_link_email": "string",
- "commission_pct": 100,
- "expenses_reimbursable": false,
- "revision_rounds": 0,
- "include_usage_rights": true,
- "usage_rights_months": 3,
- "usage_rights_duration_days": 90,
- "usage_rights_scope": "organic_only",
- "usage_exclusivity_enabled": false,
- "usage_exclusivity_months": 3,
- "agreement_clause": "string",
- "termination_notice_days": 30,
- "governing_law_jurisdiction": "New York",
- "reward": {
- "kind": "retainer",
- "mode": "fixed",
- "payment_per_post": 0,
- "tiers": [
- {
- "name": "string",
- "retainer": 0,
- "gmv_threshold": 0
}
], - "gmv_bonus_tiers": [
- {
- "gmv_threshold": 0,
- "bonus_amount": 0
}
], - "recurring": false,
- "payment_schedule": "one_time",
- "payment_due_days": 30,
- "minimum_gmv_floor": 0
}
}, - "status": "draft",
- "brand_signed_at": "2019-08-24T14:15:22Z",
- "creator_signed_at": "2019-08-24T14:15:22Z",
- "effective_date": "2019-08-24",
- "expiry_date": "2019-08-24",
- "last_error": "string"
}A short-lived signed URL for the contract PDF, for inline rendering. Resolves to the countersigned PDF once the creator has signed, and to the unsigned PDF currently out for signature before that.
404 when the contract does not belong to this shop, or when neither PDF has been generated yet (a draft that was never sent). Single-shop only.
| contract_id required | string <uuid> (Contract Id) Contract id (UUID). |
{- "property1": "string",
- "property2": "string"
}The immutable HTML snapshot of the contract as it was sent for signature, returned as text/html rather than JSON. This is the rendered document body — use it to display or archive the exact terms the creator was shown, without going through a signed URL. Signature blocks are part of the PDF, not this snapshot; fetch the countersigned copy from /download.
404 when the contract does not belong to this shop, or when the document has not been generated yet. Single-shop only.
| contract_id required | string <uuid> (Contract Id) Contract id (UUID). |
{- "detail": [
- {
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}A short-lived signed URL that downloads the fully-executed PDF (attachment disposition, named after the contract title).
Unlike /view-url this never falls back to the unsigned document: 404 until the creator has signed, and 404 when the contract does not belong to this shop. Single-shop only.
| contract_id required | string <uuid> (Contract Id) Contract id (UUID). |
{- "property1": "string",
- "property2": "string"
}Every spark code collected for the shop, newest first.
This is not /gmv-max/spark-codes. That endpoint returns the queue of codes waiting to be pushed to TikTok, which is empty until /gmv-max/spark-codes/sync-all is run. This one returns the codes themselves, including the ones creators submit through the spark-code form an automation DMs them.
source says where a code came from: form (submitted through the form, or entered by hand), scraped (found on the creator's video), or bulk_upload (a retainer CSV). form_uuid ties a row back to the form link the creator was sent.
permission reflects the checkbox on the form and is not a usage-rights grant — usage rights are a separate flow with their own statuses, exposed under /usage-rights.
Creator contact details captured by the same form (email, address, phone) are deliberately not returned here.
Single shop only — set x-shop-id to one shop ID.
Automation Id (integer) or Automation Id (null) (Automation Id) Only codes collected by this automation. | |
Creator Id (string) or Creator Id (null) (Creator Id) Only codes from this TikTok creator ID. | |
Source (string) or Source (null) (Source) Restrict to one source: | |
Start Date (string) or Start Date (null) (Start Date) Only codes collected on or after this date (YYYY-MM-DD). | |
End Date (string) or End Date (null) (End Date) Only codes collected on or before this date (YYYY-MM-DD). | |
| page | integer (Page) >= 1 Default: 1 |
| page_size | integer (Page Size) [ 1 .. 100 ] Default: 50 |
{- "data": [
- {
- "spark_code": "string",
- "creator_id": "string",
- "creator_handle": "string",
- "video_id": "string",
- "video_url": "string",
- "product_name": "string",
- "automation_id": 0,
- "form_uuid": "string",
- "permission": true,
- "source": "string",
- "collected_at": "string",
- "expires_at": "string"
}
], - "pagination": {
- "total_count": 0,
- "page": 0,
- "page_size": 0,
- "total_pages": 0
}
}