{"openapi":"3.0.1","info":{"title":"Traackr API v1","description":"# Introduction\n\nConnect Traackr data directly to the tools and systems your team already uses — no manual exports, no offline workarounds. The Traackr API is designed to reduce friction and help your team make faster, better-informed decisions.\n\n---\n\n## API Use Cases\n\nThe Traackr API is initially designed to help you:\n\n- **Analyze campaign performance & ROI** — Access post- and campaign-level metrics, aggregated by creator, tier, brand, or market — without processing raw data exports to make faster, better-informed decisions.\n- **Govern spend and reconcile financials** — Retrieve spend, deliverables, and payments data to compare campaign investment against outcomes and integrate directly with finance systems.\n- **Automate compliance and governance** — Programmatically validate campaign configurations, enforce naming conventions, and detect incomplete setups across regions — replacing manual audit processes.\n\n---\n\n## Current Scope — Phase 1: Campaign Domain\n\n**Phase 1 covers the following data access capabilities:**\n\n- Campaigns and campaign groups\n- Campaign configuration and metadata\n- Creators associated with campaigns\n- Creator profiles, including account-scoped data (e.g. tags, funnel stage, review status, custom fields)\n- Campaign posts and deliverables, including spend data\n\n**Phase 1 also introduces query and aggregation capabilities:**\n\n- Filtering, sorting, and limiting across major data collections\n- Native aggregated KPIs (e.g. standard statistical aggregations across creators, tiers, markets, and timeframes)\n\nThese capabilities are intended to reduce dependency on raw data exports and enable precise, programmatic data retrieval.\n\nCapabilities beyond the Campaign domain — including benchmark data, discovery, and additional domains — are planned for future phases and are not in scope for this guide.\n\n---\n\n## Pilot & Beta — Important Notice\n\n> [!important]\n> The Traackr API is currently in pilot and beta. It is available to a limited set of internal users and is under active development. Functionality, endpoints, and data contracts may change prior to General Availability (GA).\n\n**Once the API reaches GA, the following change policy will apply:**\n\n- Additive changes are not considered breaking changes. Traackr reserves the right to add new fields, properties, or data points to existing response schemas at any time, without prior notice. Notifications of such improvements may be shared in release communications, but this is not guaranteed. Integrations must be built to tolerate unknown fields gracefully — **do not implement strict deserialization rules** that fail when unexpected fields are present.\n- Structural or breaking changes will be versioned. Any change that would break existing integrations — such as removing or renaming fields, changing data types, or restructuring response schemas — will be introduced through a new API version. Adequate migration time will be provided before any prior version is deprecated.\n\n---\n\n## Support\n\nFor questions, issues, or feedback during the pilot and beta, please reach out to the API team at [api-support@traackr.com](mailto:api-support@traackr.com).\n\n---\n\n## Authentication\n\nEvery request must include an `X-Api-Key` header. API keys are scoped to a single account and provisioned by Traackr.\n\n---\n\n## Conventions\n\n- Responses are JSON with `snake_case` field names.\n- Timestamps are ISO-8601 UTC (`2024-09-13T17:12:06Z`).\n- List endpoints paginate with `cursor` + `limit`; the next page's cursor is in `page.next_cursor`.\n- Errors return a JSON body with a machine-readable `code` and human-readable `message`.","version":"v1"},"servers":[{"url":"https://api.traackr.ai/api","description":"Public API"}],"security":[{"ApiKey":[]}],"tags":[{"name":"Creators","description":"Creators and their profile data."},{"name":"Campaigns","description":"Campaigns, their creators and posts, and the live aggregation of the per-post metrics at campaign and creator grain, where counters are sums and rates are recomputed from the summed parts."},{"name":"Campaign Spend","description":"Campaign spend totals, per-creator breakdowns, and line items."}],"paths":{"/v1/campaigns":{"get":{"tags":["Campaigns"],"summary":"List campaigns","description":"Returns a paginated list of campaigns visible to the authenticated account.","operationId":"listCampaigns","parameters":[{"name":"start_date_from","in":"query","description":"Filter campaigns whose start date is on or after this date (inclusive, ISO-8601).","required":false,"schema":{"type":"string","format":"date"}},{"name":"start_date_to","in":"query","description":"Filter campaigns whose start date is on or before this date (inclusive, ISO-8601).","required":false,"schema":{"type":"string","format":"date"}},{"name":"end_date_from","in":"query","description":"Filter campaigns whose end date is on or after this date (inclusive, ISO-8601).","required":false,"schema":{"type":"string","format":"date"}},{"name":"end_date_to","in":"query","description":"Filter campaigns whose end date is on or before this date (inclusive, ISO-8601).","required":false,"schema":{"type":"string","format":"date"}},{"name":"brand_id","in":"query","description":"Filter to campaigns belonging to the given brand id.","required":false,"schema":{"type":"integer","format":"int32"}},{"name":"refreshed_at_from","in":"query","description":"Filter campaigns whose report was last refreshed on or after this timestamp (inclusive, ISO-8601 UTC).","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"refreshed_at_to","in":"query","description":"Filter campaigns whose report was last refreshed on or before this timestamp (inclusive, ISO-8601 UTC).","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"category","in":"query","description":"Filter to campaigns whose category matches this value (case-sensitive, exact match).","required":false,"schema":{"type":"string"}},{"name":"country_iso","in":"query","description":"Filter to campaigns whose target country matches one of the given ISO 3166-1 alpha-2 codes. Repeat the parameter to filter by multiple countries (e.g. `?country_iso=US&country_iso=FR`). A campaign with several countries matches if any of its countries is requested.","required":false,"schema":{"uniqueItems":true,"type":"array","items":{"type":"string"}}},{"name":"sort_by","in":"query","description":"Sort field. `created_at` (default), `start_date`, `end_date`, `spend`, or any field of the `stats` object. Campaigns with no value for the sort field appear last in both sort directions.","required":false,"schema":{"type":"string","default":"created_at","enum":["created_at","start_date","end_date","posts","creators","activated_creators","response_rate","engagements","video_views","spend","engagement_rate"]}},{"name":"sort_order","in":"query","description":"Sort direction. One of `desc` (default) or `asc`.","required":false,"schema":{"type":"string","default":"desc","enum":["asc","desc"]}},{"name":"cursor","in":"query","description":"Opaque pagination cursor returned by the previous response's `page.next_cursor`. Must be used with the same sort parameters as the request that produced it.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Maximum number of campaigns to return per page (default 20).","required":false,"schema":{"type":"integer","format":"int32","default":20}}],"responses":{"500":{"description":"Unexpected server error. Include `request_id` when reporting to support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INTERNAL_SERVER_ERROR","message":"An unexpected error occurred.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["java.lang.Exception"]},"403":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"FORBIDDEN","message":"Missing or invalid API key.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ForbiddenException"]},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"RESOURCE_NOT_FOUND","message":"Resource not found.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ResourceNotFoundException"]},"504":{"description":"The request was abandoned because it exceeded its time ceiling. Returned by the live-aggregation routes, whose cost scales with the number of posts on the campaign. Retrying the same request will usually fail the same way; narrow it with filters instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"GATEWAY_TIMEOUT","message":"Aggregation exceeded its time limit.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.GatewayTimeoutException"]},"400":{"description":"Invalid request. See `details` for per-field validation errors.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INVALID_REQUEST","message":"limit must be between 1 and 100","details":[{"field":"limit","message":"must be between 1 and 100"}],"request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["org.springframework.web.method.annotation.MethodArgumentTypeMismatchException"]},"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignsResponse"}}}}}}},"/v1/campaigns/{campaignId}":{"get":{"tags":["Campaigns"],"summary":"Get a campaign","description":"Returns full detail for a single campaign by id.","operationId":"getCampaign","parameters":[{"name":"campaignId","in":"path","description":"Numeric campaign id.","required":true,"schema":{"type":"integer","format":"int32"}}],"responses":{"500":{"description":"Unexpected server error. Include `request_id` when reporting to support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INTERNAL_SERVER_ERROR","message":"An unexpected error occurred.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["java.lang.Exception"]},"403":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"FORBIDDEN","message":"Missing or invalid API key.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ForbiddenException"]},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"RESOURCE_NOT_FOUND","message":"Resource not found.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ResourceNotFoundException"]},"504":{"description":"The request was abandoned because it exceeded its time ceiling. Returned by the live-aggregation routes, whose cost scales with the number of posts on the campaign. Retrying the same request will usually fail the same way; narrow it with filters instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"GATEWAY_TIMEOUT","message":"Aggregation exceeded its time limit.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.GatewayTimeoutException"]},"400":{"description":"Invalid request. See `details` for per-field validation errors.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INVALID_REQUEST","message":"limit must be between 1 and 100","details":[{"field":"limit","message":"must be between 1 and 100"}],"request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["org.springframework.web.method.annotation.MethodArgumentTypeMismatchException"]},"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignDetail"}}}}}}},"/v1/campaigns/{campaignId}/creators":{"get":{"tags":["Campaigns"],"summary":"List creators on a campaign","description":"Returns a paginated list of creators participating in the given campaign.","operationId":"listCampaignCreators","parameters":[{"name":"campaignId","in":"path","description":"Numeric campaign id.","required":true,"schema":{"type":"integer","format":"int32"}},{"name":"audience_min","in":"query","description":"Filter to creators whose audience is at least this size.","required":false,"schema":{"type":"integer","format":"int64"}},{"name":"audience_max","in":"query","description":"Filter to creators whose audience is at most this size.","required":false,"schema":{"type":"integer","format":"int64"}},{"name":"country_iso","in":"query","description":"Filter to creators whose primary location country matches this ISO 3166-1 alpha-2 code.","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor returned by the previous response's `page.next_cursor`.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Maximum number of creators to return per page (default 20).","required":false,"schema":{"type":"integer","format":"int32","default":20}}],"responses":{"500":{"description":"Unexpected server error. Include `request_id` when reporting to support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INTERNAL_SERVER_ERROR","message":"An unexpected error occurred.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["java.lang.Exception"]},"403":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"FORBIDDEN","message":"Missing or invalid API key.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ForbiddenException"]},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"RESOURCE_NOT_FOUND","message":"Resource not found.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ResourceNotFoundException"]},"504":{"description":"The request was abandoned because it exceeded its time ceiling. Returned by the live-aggregation routes, whose cost scales with the number of posts on the campaign. Retrying the same request will usually fail the same way; narrow it with filters instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"GATEWAY_TIMEOUT","message":"Aggregation exceeded its time limit.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.GatewayTimeoutException"]},"400":{"description":"Invalid request. See `details` for per-field validation errors.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INVALID_REQUEST","message":"limit must be between 1 and 100","details":[{"field":"limit","message":"must be between 1 and 100"}],"request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["org.springframework.web.method.annotation.MethodArgumentTypeMismatchException"]},"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignCreatorsResponse"}}}}}}},"/v1/campaigns/{campaignId}/posts":{"get":{"tags":["Campaigns"],"summary":"List posts on a campaign","description":"Returns a paginated list of posts (content from social media) associated with the given campaign, sortable by any normalized metric. Normalized metrics are grouped under a `metrics` object, and each post carries the platform's own pre-normalization figures in `raw_metrics`.","operationId":"listCampaignPosts","parameters":[{"name":"campaignId","in":"path","description":"Numeric campaign id.","required":true,"schema":{"type":"integer","format":"int32"}},{"name":"platform","in":"query","description":"Filter to posts published on the given social platform (e.g. `youtube`, `instagram`, `tiktok`, `facebook`).","required":false,"schema":{"type":"string"}},{"name":"creator_id","in":"query","description":"Filter to posts published by the given creator id (32-character hexadecimal value).","required":false,"schema":{"type":"string"}},{"name":"content_type","in":"query","description":"Filter to posts with the given content type (e.g. `video`, `image`, `text`, `album`).","required":false,"schema":{"type":"string"}},{"name":"publication_type","in":"query","description":"Filter to posts with the given publication type (e.g. `stream`, `feed`, `story`, `clip`).","required":false,"schema":{"type":"string"}},{"name":"published_at_from","in":"query","description":"Filter posts published on or after this timestamp (inclusive, ISO-8601 UTC).","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"published_at_to","in":"query","description":"Filter posts published on or before this timestamp (inclusive, ISO-8601 UTC).","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"sort_by","in":"query","description":"Sort field. `published_at` (default) or any field of the `metrics` object. Posts that have no value for the sort field appear last in both sort directions.","required":false,"schema":{"type":"string","default":"published_at","enum":["published_at","engagements","reactions","comments","shares","saves","video_views","impressions","actualized_reach","potential_reach","engagement_rate","vit","video_views_rate","vitality_rate","organic_engagements","organic_video_views","organic_impressions","paid_engagements","paid_video_views","paid_impressions"]}},{"name":"sort_order","in":"query","description":"Sort direction. One of `desc` (default) or `asc`.","required":false,"schema":{"type":"string","default":"desc","enum":["asc","desc"]}},{"name":"cursor","in":"query","description":"Opaque pagination cursor returned by the previous response's `page.next_cursor`. Must be used with the same sort parameters as the request that produced it.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Maximum number of posts to return per page (default 20).","required":false,"schema":{"type":"integer","format":"int32","default":20}}],"responses":{"500":{"description":"Unexpected server error. Include `request_id` when reporting to support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INTERNAL_SERVER_ERROR","message":"An unexpected error occurred.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["java.lang.Exception"]},"403":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"FORBIDDEN","message":"Missing or invalid API key.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ForbiddenException"]},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"RESOURCE_NOT_FOUND","message":"Resource not found.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ResourceNotFoundException"]},"504":{"description":"The request was abandoned because it exceeded its time ceiling. Returned by the live-aggregation routes, whose cost scales with the number of posts on the campaign. Retrying the same request will usually fail the same way; narrow it with filters instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"GATEWAY_TIMEOUT","message":"Aggregation exceeded its time limit.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.GatewayTimeoutException"]},"400":{"description":"Invalid request. See `details` for per-field validation errors.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INVALID_REQUEST","message":"limit must be between 1 and 100","details":[{"field":"limit","message":"must be between 1 and 100"}],"request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["org.springframework.web.method.annotation.MethodArgumentTypeMismatchException"]},"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignPostsResponse"}}}}}}},"/v1/campaigns/{campaignId}/metrics":{"get":{"tags":["Campaigns"],"summary":"Get aggregated campaign metrics","description":"Sums the normalized metrics of every post on the campaign and recomputes the rates from the summed parts. Reach is cumulative across posts, so it is not a unique-audience figure, and each rate is summed only over the posts that report both its numerator and a positive reach — see the individual field descriptions.","operationId":"getCampaignMetrics","parameters":[{"name":"campaignId","in":"path","description":"Numeric campaign id.","required":true,"schema":{"type":"integer","format":"int32"}},{"name":"platform","in":"query","description":"Filter to posts published on the given social platform (e.g. `youtube`, `instagram`, `tiktok`, `facebook`). Narrows which posts are summed. Applied before the aggregation, so a filter changes every counter AND every rate's denominator.","required":false,"schema":{"type":"string"}},{"name":"content_type","in":"query","description":"Filter to posts with the given content type (e.g. `video`, `image`, `text`, `album`). Narrows which posts are summed. Applied before the aggregation, so a filter changes every counter AND every rate's denominator.","required":false,"schema":{"type":"string"}},{"name":"publication_type","in":"query","description":"Filter to posts with the given publication type (e.g. `stream`, `feed`, `story`, `clip`). Narrows which posts are summed. Applied before the aggregation, so a filter changes every counter AND every rate's denominator.","required":false,"schema":{"type":"string"}},{"name":"published_at_from","in":"query","description":"Filter posts published on or after this timestamp (inclusive, ISO-8601 UTC). Narrows which posts are summed. Applied before the aggregation, so a filter changes every counter AND every rate's denominator.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"published_at_to","in":"query","description":"Filter posts published on or before this timestamp (inclusive, ISO-8601 UTC). Narrows which posts are summed. Applied before the aggregation, so a filter changes every counter AND every rate's denominator.","required":false,"schema":{"type":"string","format":"date-time"}}],"responses":{"500":{"description":"Unexpected server error. Include `request_id` when reporting to support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INTERNAL_SERVER_ERROR","message":"An unexpected error occurred.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["java.lang.Exception"]},"403":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"FORBIDDEN","message":"Missing or invalid API key.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ForbiddenException"]},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"RESOURCE_NOT_FOUND","message":"Resource not found.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ResourceNotFoundException"]},"504":{"description":"The request was abandoned because it exceeded its time ceiling. Returned by the live-aggregation routes, whose cost scales with the number of posts on the campaign. Retrying the same request will usually fail the same way; narrow it with filters instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"GATEWAY_TIMEOUT","message":"Aggregation exceeded its time limit.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.GatewayTimeoutException"]},"400":{"description":"Invalid request. See `details` for per-field validation errors.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INVALID_REQUEST","message":"limit must be between 1 and 100","details":[{"field":"limit","message":"must be between 1 and 100"}],"request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["org.springframework.web.method.annotation.MethodArgumentTypeMismatchException"]},"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignMetrics"}}}}}}},"/v1/campaigns/{campaignId}/creators/metrics":{"get":{"tags":["Campaigns"],"summary":"List per-creator aggregated metrics on a campaign","description":"Returns a paginated list of per-creator metric aggregations, sortable by any served metric. **Returns only creators with at least one post matching the filters.** For a specific creator, including one with no posts, use `/campaigns/{campaignId}/creators/{creatorId}/metrics`, which answers 200 with zero counts and null rates rather than 404. To list every creator on the campaign regardless of whether they posted, use `/campaigns/{campaignId}/creators`.","operationId":"listCampaignCreatorsMetrics","parameters":[{"name":"campaignId","in":"path","description":"Numeric campaign id.","required":true,"schema":{"type":"integer","format":"int32"}},{"name":"platform","in":"query","description":"Filter to posts published on the given social platform (e.g. `youtube`, `instagram`, `tiktok`, `facebook`). Narrows which posts are summed. Applied before the aggregation, so a filter changes every counter AND every rate's denominator.","required":false,"schema":{"type":"string"}},{"name":"content_type","in":"query","description":"Filter to posts with the given content type (e.g. `video`, `image`, `text`, `album`). Narrows which posts are summed. Applied before the aggregation, so a filter changes every counter AND every rate's denominator.","required":false,"schema":{"type":"string"}},{"name":"publication_type","in":"query","description":"Filter to posts with the given publication type (e.g. `stream`, `feed`, `story`, `clip`). Narrows which posts are summed. Applied before the aggregation, so a filter changes every counter AND every rate's denominator.","required":false,"schema":{"type":"string"}},{"name":"published_at_from","in":"query","description":"Filter posts published on or after this timestamp (inclusive, ISO-8601 UTC). Narrows which posts are summed. Applied before the aggregation, so a filter changes every counter AND every rate's denominator.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"published_at_to","in":"query","description":"Filter posts published on or before this timestamp (inclusive, ISO-8601 UTC). Narrows which posts are summed. Applied before the aggregation, so a filter changes every counter AND every rate's denominator.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"sort_by","in":"query","description":"Sort field. Any served metric; creators whose value is null sort last in both directions.","required":false,"schema":{"type":"string","default":"engagements","enum":["post_count","measured_post_count","engagements","reactions","comments","shares","saves","video_views","impressions","actualized_reach","potential_reach","engagement_rate","vit","video_views_rate","vitality_rate","organic_engagements","organic_video_views","organic_impressions","paid_engagements","paid_video_views","paid_impressions"]}},{"name":"sort_order","in":"query","description":"Sort direction. One of `desc` (default) or `asc`.","required":false,"schema":{"type":"string","default":"desc","enum":["asc","desc"]}},{"name":"cursor","in":"query","description":"Opaque pagination cursor returned by the previous response's `page.next_cursor`. Must be used with the same sort AND filter parameters as the request that produced it: these sort keys are recomputed from the filtered set, so a cursor replayed under a different scope is rejected with `400` rather than silently repeating rows.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Maximum number of creators to return per page (default 20).","required":false,"schema":{"type":"integer","format":"int32","default":20}}],"responses":{"500":{"description":"Unexpected server error. Include `request_id` when reporting to support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INTERNAL_SERVER_ERROR","message":"An unexpected error occurred.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["java.lang.Exception"]},"403":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"FORBIDDEN","message":"Missing or invalid API key.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ForbiddenException"]},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"RESOURCE_NOT_FOUND","message":"Resource not found.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ResourceNotFoundException"]},"504":{"description":"The request was abandoned because it exceeded its time ceiling. Returned by the live-aggregation routes, whose cost scales with the number of posts on the campaign. Retrying the same request will usually fail the same way; narrow it with filters instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"GATEWAY_TIMEOUT","message":"Aggregation exceeded its time limit.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.GatewayTimeoutException"]},"400":{"description":"Invalid request. See `details` for per-field validation errors.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INVALID_REQUEST","message":"limit must be between 1 and 100","details":[{"field":"limit","message":"must be between 1 and 100"}],"request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["org.springframework.web.method.annotation.MethodArgumentTypeMismatchException"]},"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignCreatorsMetricsResponse"}}}}}}},"/v1/campaigns/{campaignId}/creators/{creatorId}/metrics":{"get":{"tags":["Campaigns"],"summary":"Get a creator's aggregated metrics on a campaign","description":"Returns one creator's metric aggregation on the campaign — the same values as that creator's row on `/creators/metrics` for identical filters. A creator who is on the campaign but has no post in scope returns 200 with zero counts and null rates; 404 is reserved for a creator who is not on the campaign at all, so callers can tell \"nothing delivered\" from \"not contracted\".","operationId":"getCreatorMetrics","parameters":[{"name":"campaignId","in":"path","description":"Numeric campaign id.","required":true,"schema":{"type":"integer","format":"int32"}},{"name":"creatorId","in":"path","description":"Creator profile id (32-character hexadecimal value).","required":true,"schema":{"type":"string"}},{"name":"platform","in":"query","description":"Filter to posts published on the given social platform (e.g. `youtube`, `instagram`, `tiktok`, `facebook`). Narrows which posts are summed. Applied before the aggregation, so a filter changes every counter AND every rate's denominator.","required":false,"schema":{"type":"string"}},{"name":"content_type","in":"query","description":"Filter to posts with the given content type (e.g. `video`, `image`, `text`, `album`). Narrows which posts are summed. Applied before the aggregation, so a filter changes every counter AND every rate's denominator.","required":false,"schema":{"type":"string"}},{"name":"publication_type","in":"query","description":"Filter to posts with the given publication type (e.g. `stream`, `feed`, `story`, `clip`). Narrows which posts are summed. Applied before the aggregation, so a filter changes every counter AND every rate's denominator.","required":false,"schema":{"type":"string"}},{"name":"published_at_from","in":"query","description":"Filter posts published on or after this timestamp (inclusive, ISO-8601 UTC). Narrows which posts are summed. Applied before the aggregation, so a filter changes every counter AND every rate's denominator.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"published_at_to","in":"query","description":"Filter posts published on or before this timestamp (inclusive, ISO-8601 UTC). Narrows which posts are summed. Applied before the aggregation, so a filter changes every counter AND every rate's denominator.","required":false,"schema":{"type":"string","format":"date-time"}}],"responses":{"500":{"description":"Unexpected server error. Include `request_id` when reporting to support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INTERNAL_SERVER_ERROR","message":"An unexpected error occurred.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["java.lang.Exception"]},"403":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"FORBIDDEN","message":"Missing or invalid API key.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ForbiddenException"]},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"RESOURCE_NOT_FOUND","message":"Resource not found.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ResourceNotFoundException"]},"504":{"description":"The request was abandoned because it exceeded its time ceiling. Returned by the live-aggregation routes, whose cost scales with the number of posts on the campaign. Retrying the same request will usually fail the same way; narrow it with filters instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"GATEWAY_TIMEOUT","message":"Aggregation exceeded its time limit.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.GatewayTimeoutException"]},"400":{"description":"Invalid request. See `details` for per-field validation errors.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INVALID_REQUEST","message":"limit must be between 1 and 100","details":[{"field":"limit","message":"must be between 1 and 100"}],"request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["org.springframework.web.method.annotation.MethodArgumentTypeMismatchException"]},"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorMetrics"}}}}}}},"/v1/campaigns/{campaignId}/spend":{"get":{"tags":["Campaign Spend"],"summary":"Get campaign spend totals","description":"Returns campaign-wide spend totals and the additional-cost category breakdown.","operationId":"getCampaignSpend","parameters":[{"name":"campaignId","in":"path","description":"Numeric campaign id.","required":true,"schema":{"type":"integer","format":"int32"}}],"responses":{"500":{"description":"Unexpected server error. Include `request_id` when reporting to support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INTERNAL_SERVER_ERROR","message":"An unexpected error occurred.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["java.lang.Exception"]},"403":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"FORBIDDEN","message":"Missing or invalid API key.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ForbiddenException"]},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"RESOURCE_NOT_FOUND","message":"Resource not found.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ResourceNotFoundException"]},"504":{"description":"The request was abandoned because it exceeded its time ceiling. Returned by the live-aggregation routes, whose cost scales with the number of posts on the campaign. Retrying the same request will usually fail the same way; narrow it with filters instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"GATEWAY_TIMEOUT","message":"Aggregation exceeded its time limit.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.GatewayTimeoutException"]},"400":{"description":"Invalid request. See `details` for per-field validation errors.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INVALID_REQUEST","message":"limit must be between 1 and 100","details":[{"field":"limit","message":"must be between 1 and 100"}],"request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["org.springframework.web.method.annotation.MethodArgumentTypeMismatchException"]},"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignSpend"}}}}}}},"/v1/campaigns/{campaignId}/creators/spend":{"get":{"tags":["Campaign Spend"],"summary":"List per-creator spend on a campaign","description":"Returns a paginated list of per-creator spend aggregations for the given campaign.","operationId":"listCampaignCreatorsSpend","parameters":[{"name":"campaignId","in":"path","description":"Numeric campaign id.","required":true,"schema":{"type":"integer","format":"int32"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor returned by the previous response's `page.next_cursor`.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Maximum number of creators to return per page (default 20).","required":false,"schema":{"type":"integer","format":"int32","default":20}}],"responses":{"500":{"description":"Unexpected server error. Include `request_id` when reporting to support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INTERNAL_SERVER_ERROR","message":"An unexpected error occurred.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["java.lang.Exception"]},"403":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"FORBIDDEN","message":"Missing or invalid API key.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ForbiddenException"]},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"RESOURCE_NOT_FOUND","message":"Resource not found.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ResourceNotFoundException"]},"504":{"description":"The request was abandoned because it exceeded its time ceiling. Returned by the live-aggregation routes, whose cost scales with the number of posts on the campaign. Retrying the same request will usually fail the same way; narrow it with filters instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"GATEWAY_TIMEOUT","message":"Aggregation exceeded its time limit.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.GatewayTimeoutException"]},"400":{"description":"Invalid request. See `details` for per-field validation errors.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INVALID_REQUEST","message":"limit must be between 1 and 100","details":[{"field":"limit","message":"must be between 1 and 100"}],"request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["org.springframework.web.method.annotation.MethodArgumentTypeMismatchException"]},"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignCreatorsSpendResponse"}}}}}}},"/v1/campaigns/{campaignId}/creators/{creatorId}/spend":{"get":{"tags":["Campaign Spend"],"summary":"Get a creator's spend on a campaign","description":"Returns one creator's spend aggregations on the given campaign, including the per-platform rollup.","operationId":"getCreatorSpend","parameters":[{"name":"campaignId","in":"path","description":"Numeric campaign id.","required":true,"schema":{"type":"integer","format":"int32"}},{"name":"creatorId","in":"path","description":"Creator profile id (32-character hexadecimal value).","required":true,"schema":{"type":"string"}}],"responses":{"500":{"description":"Unexpected server error. Include `request_id` when reporting to support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INTERNAL_SERVER_ERROR","message":"An unexpected error occurred.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["java.lang.Exception"]},"403":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"FORBIDDEN","message":"Missing or invalid API key.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ForbiddenException"]},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"RESOURCE_NOT_FOUND","message":"Resource not found.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ResourceNotFoundException"]},"504":{"description":"The request was abandoned because it exceeded its time ceiling. Returned by the live-aggregation routes, whose cost scales with the number of posts on the campaign. Retrying the same request will usually fail the same way; narrow it with filters instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"GATEWAY_TIMEOUT","message":"Aggregation exceeded its time limit.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.GatewayTimeoutException"]},"400":{"description":"Invalid request. See `details` for per-field validation errors.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INVALID_REQUEST","message":"limit must be between 1 and 100","details":[{"field":"limit","message":"must be between 1 and 100"}],"request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["org.springframework.web.method.annotation.MethodArgumentTypeMismatchException"]},"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorSpend"}}}}}}},"/v1/campaigns/{campaignId}/creators/{creatorId}/deliverables":{"get":{"tags":["Campaign Spend"],"summary":"List a creator's deliverable line items on a campaign","description":"Returns a paginated list of deliverable line items for the given creator on the campaign.","operationId":"listCampaignCreatorDeliverables","parameters":[{"name":"campaignId","in":"path","description":"Numeric campaign id.","required":true,"schema":{"type":"integer","format":"int32"}},{"name":"creatorId","in":"path","description":"Creator profile id (32-character hexadecimal value).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor returned by the previous response's `page.next_cursor`.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Maximum number of line items to return per page (default 20).","required":false,"schema":{"type":"integer","format":"int32","default":20}}],"responses":{"500":{"description":"Unexpected server error. Include `request_id` when reporting to support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INTERNAL_SERVER_ERROR","message":"An unexpected error occurred.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["java.lang.Exception"]},"403":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"FORBIDDEN","message":"Missing or invalid API key.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ForbiddenException"]},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"RESOURCE_NOT_FOUND","message":"Resource not found.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ResourceNotFoundException"]},"504":{"description":"The request was abandoned because it exceeded its time ceiling. Returned by the live-aggregation routes, whose cost scales with the number of posts on the campaign. Retrying the same request will usually fail the same way; narrow it with filters instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"GATEWAY_TIMEOUT","message":"Aggregation exceeded its time limit.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.GatewayTimeoutException"]},"400":{"description":"Invalid request. See `details` for per-field validation errors.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INVALID_REQUEST","message":"limit must be between 1 and 100","details":[{"field":"limit","message":"must be between 1 and 100"}],"request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["org.springframework.web.method.annotation.MethodArgumentTypeMismatchException"]},"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignCreatorDeliverablesResponse"}}}}}}},"/v1/campaigns/{campaignId}/creators/{creatorId}/products":{"get":{"tags":["Campaign Spend"],"summary":"List products sent to a creator","description":"Returns a paginated list of products sent to the given creator on the campaign (gifting / product seeding). These roll up into the `products.total` spend bucket. Unrelated to the campaign's `product_line` metadata field.","operationId":"listCampaignCreatorProducts","parameters":[{"name":"campaignId","in":"path","description":"Numeric campaign id.","required":true,"schema":{"type":"integer","format":"int32"}},{"name":"creatorId","in":"path","description":"Creator profile id (32-character hexadecimal value).","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","description":"Opaque pagination cursor returned by the previous response's `page.next_cursor`.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Maximum number of line items to return per page (default 20).","required":false,"schema":{"type":"integer","format":"int32","default":20}}],"responses":{"500":{"description":"Unexpected server error. Include `request_id` when reporting to support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INTERNAL_SERVER_ERROR","message":"An unexpected error occurred.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["java.lang.Exception"]},"403":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"FORBIDDEN","message":"Missing or invalid API key.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ForbiddenException"]},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"RESOURCE_NOT_FOUND","message":"Resource not found.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ResourceNotFoundException"]},"504":{"description":"The request was abandoned because it exceeded its time ceiling. Returned by the live-aggregation routes, whose cost scales with the number of posts on the campaign. Retrying the same request will usually fail the same way; narrow it with filters instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"GATEWAY_TIMEOUT","message":"Aggregation exceeded its time limit.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.GatewayTimeoutException"]},"400":{"description":"Invalid request. See `details` for per-field validation errors.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INVALID_REQUEST","message":"limit must be between 1 and 100","details":[{"field":"limit","message":"must be between 1 and 100"}],"request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["org.springframework.web.method.annotation.MethodArgumentTypeMismatchException"]},"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignCreatorProductsResponse"}}}}}}},"/v1/creators/{creatorId}":{"get":{"tags":["Creators"],"summary":"Get a creator","description":"Returns a creator's global profile by creator id.","operationId":"getCreator","parameters":[{"name":"creatorId","in":"path","description":"Creator id.","required":true,"schema":{"type":"string"}}],"responses":{"500":{"description":"Unexpected server error. Include `request_id` when reporting to support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INTERNAL_SERVER_ERROR","message":"An unexpected error occurred.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["java.lang.Exception"]},"403":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"FORBIDDEN","message":"Missing or invalid API key.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ForbiddenException"]},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"RESOURCE_NOT_FOUND","message":"Resource not found.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ResourceNotFoundException"]},"504":{"description":"The request was abandoned because it exceeded its time ceiling. Returned by the live-aggregation routes, whose cost scales with the number of posts on the campaign. Retrying the same request will usually fail the same way; narrow it with filters instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"GATEWAY_TIMEOUT","message":"Aggregation exceeded its time limit.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.GatewayTimeoutException"]},"400":{"description":"Invalid request. See `details` for per-field validation errors.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INVALID_REQUEST","message":"limit must be between 1 and 100","details":[{"field":"limit","message":"must be between 1 and 100"}],"request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["org.springframework.web.method.annotation.MethodArgumentTypeMismatchException"]},"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorDetail"}}}}}}},"/v1/creators/{creatorId}/segments":{"get":{"tags":["Creators"],"summary":"Get a creator's segments","description":"Returns the calling account's segments for a creator (tags, funnel stage, review status, groups, campaigns). 404 when the creator is not in the calling account.","operationId":"getCreatorSegments","parameters":[{"name":"creatorId","in":"path","description":"Creator id.","required":true,"schema":{"type":"string"}}],"responses":{"500":{"description":"Unexpected server error. Include `request_id` when reporting to support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INTERNAL_SERVER_ERROR","message":"An unexpected error occurred.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["java.lang.Exception"]},"403":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"FORBIDDEN","message":"Missing or invalid API key.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ForbiddenException"]},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"RESOURCE_NOT_FOUND","message":"Resource not found.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ResourceNotFoundException"]},"504":{"description":"The request was abandoned because it exceeded its time ceiling. Returned by the live-aggregation routes, whose cost scales with the number of posts on the campaign. Retrying the same request will usually fail the same way; narrow it with filters instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"GATEWAY_TIMEOUT","message":"Aggregation exceeded its time limit.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.GatewayTimeoutException"]},"400":{"description":"Invalid request. See `details` for per-field validation errors.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INVALID_REQUEST","message":"limit must be between 1 and 100","details":[{"field":"limit","message":"must be between 1 and 100"}],"request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["org.springframework.web.method.annotation.MethodArgumentTypeMismatchException"]},"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorSegments"}}}}}}},"/v1/creators/{creatorId}/audience":{"get":{"tags":["Creators"],"summary":"Get a creator's audience demographics","description":"Returns per-channel audience demographics for a creator (gender/age/country distributions), ordered by descending follower count. 404 when the creator is not in the calling account; 200 with an empty `audiences` array when the creator has no analyzed channels.","operationId":"getCreatorAudience","parameters":[{"name":"creatorId","in":"path","description":"Creator id.","required":true,"schema":{"type":"string"}},{"name":"platform","in":"query","description":"Filter to the audience record(s) for this platform (e.g. `instagram`).","required":false,"schema":{"type":"string"}},{"name":"distributions","in":"query","description":"Comma-separated distribution types to include (e.g. `ages,countries`). Unknown values are ignored; every type is returned if none of the requested values are recognized.","required":false,"schema":{"uniqueItems":true,"type":"array","items":{"type":"string"}}},{"name":"top","in":"query","description":"Maximum number of values returned per distribution, ordered by descending weight (default 5, max 20).","required":false,"schema":{"type":"integer","format":"int32","default":5}}],"responses":{"500":{"description":"Unexpected server error. Include `request_id` when reporting to support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INTERNAL_SERVER_ERROR","message":"An unexpected error occurred.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["java.lang.Exception"]},"403":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"FORBIDDEN","message":"Missing or invalid API key.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ForbiddenException"]},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"RESOURCE_NOT_FOUND","message":"Resource not found.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.ResourceNotFoundException"]},"504":{"description":"The request was abandoned because it exceeded its time ceiling. Returned by the live-aggregation routes, whose cost scales with the number of posts on the campaign. Retrying the same request will usually fail the same way; narrow it with filters instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"GATEWAY_TIMEOUT","message":"Aggregation exceeded its time limit.","request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["com.traackr.api.model.exception.GatewayTimeoutException"]},"400":{"description":"Invalid request. See `details` for per-field validation errors.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"code":"INVALID_REQUEST","message":"limit must be between 1 and 100","details":[{"field":"limit","message":"must be between 1 and 100"}],"request_id":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"x-exception-class":["org.springframework.web.method.annotation.MethodArgumentTypeMismatchException"]},"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatorAudiences"}}}}}}}},"components":{"schemas":{"ErrorResponse":{"required":["code","message"],"type":"object","properties":{"code":{"type":"string","description":"Machine-readable error code. One of `FORBIDDEN` (403), `INVALID_REQUEST` (400), `RESOURCE_NOT_FOUND` (404), `INTERNAL_SERVER_ERROR` (500).","example":"INVALID_REQUEST"},"message":{"type":"string","description":"Human-readable error message describing what went wrong.","example":"limit must be between 1 and 100"},"details":{"type":"array","description":"Per-field validation errors, populated when `code` is `INVALID_REQUEST`.","items":{"$ref":"#/components/schemas/FieldErrorDetail"}},"request_id":{"type":"string","description":"Server-assigned request id. Include this when reporting an issue to Traackr support so the request can be correlated in server logs.","example":"a1b2c3d4-e5f6-7890-abcd-ef0123456789"}}},"FieldErrorDetail":{"required":["field","message"],"type":"object","properties":{"field":{"type":"string","description":"Path of the request field that failed validation.","example":"limit"},"message":{"type":"string","description":"Validation message explaining why the field is invalid.","example":"must be between 1 and 100"}},"description":"Per-field validation errors, populated when `code` is `INVALID_REQUEST`."},"CreatorChannel":{"required":["url"],"type":"object","properties":{"url":{"type":"string","description":"Public URL of the creator's channel on the platform.","example":"https://instagram.com/alicemartin"},"platform":{"type":"string","description":"Canonical platform identifier — a normalized lowercase token (e.g. `instagram`, `tiktok`, `youtube`, `facebook`, `twitter`, `twitch`, `snapchat`, `pinterest`).","example":"instagram"},"platform_user_id":{"type":"string","description":"Platform-assigned user identifier.","example":"30890516489"},"username":{"type":"string","description":"Platform handle / username (e.g. `alicebeauty`).","example":"alicemartin"},"metrics":{"$ref":"#/components/schemas/CreatorChannelMetrics"}},"description":"Channels tracked for this creator, sorted by per-channel audience descending. A creator may have multiple channels on the same platform."},"CreatorChannelMetrics":{"type":"object","properties":{"audience":{"type":"integer","description":"Per-channel follower / subscriber count — followers on Instagram, TikTok, Twitter, Facebook, Pinterest; subscribers on YouTube and Snapchat.","format":"int64","example":52100},"engagement_rate":{"type":"number","description":"The level of engagement the creator's content is receiving from their audience. Calculated by dividing total engagements by potential reach. Reported as a decimal ratio (e.g. `0.045` = 4.5%). See https://help.traackr.com/en/articles/2817311-engagement-rate.","format":"double","example":0.045},"post_count":{"type":"integer","description":"Total number of posts on the channel. Absent on platforms that do not expose a post-count metric (Facebook, LinkedIn, Reddit, Snapchat, Weibo, Naver, Twitch, Clubhouse).","format":"int64","example":2590},"posts_per_day":{"type":"number","description":"Average number of posts per day on the channel, computed over the last 6 months.","format":"double","example":1.5},"video_views_rate":{"type":"number","description":"The level of video views the creator's content is receiving from their audience. Calculated by dividing the sum of all video views by potential reach. Absent on text-first channels.","format":"double","example":0.32}},"description":"Aggregate channel metrics. Available cross-platform metrics are normalized into a common shape; not every metric is populated on every platform."},"CreatorDetail":{"required":["channels","id","uid_aliases"],"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the creator profile in Traackr (32-character hexadecimal value).","example":"00b2c4e1706841ff88f91848ffa6625f"},"name":{"type":"string","description":"Creator's name as it appears on their Traackr profile (the overall profile name, not their per-platform handle).","example":"Alice Martin"},"description":{"type":"string","description":"Free-text description on the creator's Traackr profile.","example":"Beauty creator focused on clean cosmetics and skincare routines."},"audience":{"type":"integer","description":"Total audience across all of the creator's tracked channels (sum of each entry's `metrics.audience`).","format":"int64","example":56995},"location":{"$ref":"#/components/schemas/CreatorLocation"},"channels":{"type":"array","description":"Channels tracked for this creator, sorted by per-channel audience descending. A creator may have multiple channels on the same platform.","items":{"$ref":"#/components/schemas/CreatorChannel"}},"uid_aliases":{"type":"array","description":"Former creator ids (aliases) that resolve to this profile. When a creator profile is merged, the discarded id is kept as an alias so stale references in older exports or reports continue to resolve to the canonical profile.","example":["a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"],"items":{"type":"string","description":"Former creator ids (aliases) that resolve to this profile. When a creator profile is merged, the discarded id is kept as an alias so stale references in older exports or reports continue to resolve to the canonical profile.","example":"[\"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4\"]"}}}},"CreatorLocation":{"type":"object","properties":{"display":{"type":"string","description":"Creator's full display location as captured on their Traackr profile. Free-form (e.g. `Brooklyn, NY, US`).","example":"Brooklyn, NY, US"},"country":{"type":"string","description":"Creator's country name in plain text (e.g. `United States`). Free-form, not an ISO code.","example":"United States"},"country_iso":{"type":"string","description":"Creator's country as an ISO 3166-1 alpha-2 code (two-letter uppercase, e.g. `US`, `FR`, `JP`).","example":"US"}},"description":"Creator's location as captured on their Traackr profile."},"CreatorSegment":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"Numeric identifier of the entity within the calling account.","format":"int32","example":42},"label":{"type":"string","description":"Display name of the funnel stage, review status, group, or campaign. Null for a group that has no name; the other segment types always carry a label.","example":"Engaged"}},"description":"Campaigns of the calling account this creator is part of. Empty when none."},"CreatorSegments":{"required":["campaigns","groups","tags"],"type":"object","properties":{"tags":{"type":"array","description":"Free-form tags the account has applied to this creator, in original case. Empty when the account has tagged nothing.","example":["VIP","Summer Campaign 2025","high-engagement"],"items":{"type":"string","description":"Free-form tags the account has applied to this creator, in original case. Empty when the account has tagged nothing.","example":"[\"VIP\",\"Summer Campaign 2025\",\"high-engagement\"]"}},"funnel_stage":{"$ref":"#/components/schemas/CreatorSegment"},"review_status":{"$ref":"#/components/schemas/CreatorSegment"},"groups":{"type":"array","description":"Influencer groups the account has placed this creator in. Empty when none.","items":{"$ref":"#/components/schemas/CreatorSegment"}},"campaigns":{"type":"array","description":"Campaigns of the calling account this creator is part of. Empty when none.","items":{"$ref":"#/components/schemas/CreatorSegment"}}}},"CreatorAudienceChannel":{"required":["platform_user_id"],"type":"object","properties":{"url":{"type":"string","description":"Public URL of the creator's channel on the platform. Null when this channel isn't currently on the creator's tracked channel list.","example":"https://instagram.com/alicemartin"},"username":{"type":"string","description":"Platform handle / username. Null when this channel isn't currently on the creator's tracked channel list.","example":"alicemartin"},"platform_user_id":{"type":"string","description":"Platform-assigned user identifier.","example":"30890516489"}},"description":"The channel this analysis belongs to."},"CreatorAudienceCredibility":{"type":"object","properties":{"from_followers":{"type":"number","description":"Credibility score computed over the followers segment. Null when unavailable.","format":"double","example":0.725142},"from_engaged_audience":{"type":"number","description":"Credibility score computed over the engaged/liker audience — Instagram only. Null off Instagram or when unavailable.","format":"double","example":0.895142}},"description":"Audience credibility (bot/fake-follower resistance) scores."},"CreatorAudienceDistribution":{"required":["type","values"],"type":"object","properties":{"type":{"type":"string","description":"Which demographic breakdown this is: `genders`, `ages`, or `countries` today. Additional types may be added over time; consumers should tolerate unknown values.","example":"ages"},"values":{"type":"array","description":"Values ordered by descending weight, capped at the requested `top`.","items":{"$ref":"#/components/schemas/CreatorAudienceDistributionValue"}}},"description":"Weighted demographic breakdowns for this channel's followers. Present for `available`/`incomplete` statuses; empty otherwise. Not every distribution type is guaranteed to be present for every record."},"CreatorAudienceDistributionValue":{"required":["key","weight"],"type":"object","properties":{"key":{"type":"string","description":"Canonical value for this distribution (e.g. `female`, `18_24`, `US`).","example":"US"},"weight":{"type":"number","description":"Audience share for this value, decimal in [0, 1]. Reflects the followers audience only.","format":"double","example":0.493004}},"description":"Values ordered by descending weight, capped at the requested `top`."},"CreatorAudienceRecord":{"required":["channel","credibility","distributions","platform","status"],"type":"object","properties":{"platform":{"type":"string","description":"Canonical lowercase platform identifier.","example":"instagram"},"channel":{"$ref":"#/components/schemas/CreatorAudienceChannel"},"status":{"$ref":"#/components/schemas/CreatorAudienceStatus"},"credibility":{"$ref":"#/components/schemas/CreatorAudienceCredibility"},"notable_users_ratio":{"type":"number","description":"Share of notable/influential accounts in the follower audience, decimal in [0, 1]. Null when unavailable.","format":"double","example":0.223805},"distributions":{"type":"array","description":"Weighted demographic breakdowns for this channel's followers. Present for `available`/`incomplete` statuses; empty otherwise. Not every distribution type is guaranteed to be present for every record.","items":{"$ref":"#/components/schemas/CreatorAudienceDistribution"}}},"description":"One entry per analyzed channel, ordered by descending follower count. Empty when the creator has no analyzed channels."},"CreatorAudienceStatus":{"required":["type"],"type":"object","properties":{"type":{"type":"string","description":"One of `available`, `incomplete`, `unavailable`, `ineligible`, `pending`. Consumers should tolerate unknown values.","example":"available"},"reason":{"type":"string","description":"Optional free-text detail on the status. Null when not provided.","example":"likers: empty_audience"}},"description":"Availability of this analysis."},"CreatorAudiences":{"required":["audiences"],"type":"object","properties":{"audiences":{"type":"array","description":"One entry per analyzed channel, ordered by descending follower count. Empty when the creator has no analyzed channels.","items":{"$ref":"#/components/schemas/CreatorAudienceRecord"}}}},"Brand":{"required":["id"],"type":"object","properties":{"id":{"type":"integer","description":"Numeric identifier for the brand in Traackr.","format":"int32","example":11677},"name":{"type":"string","description":"Brand display name as configured in the account's brand settings.","example":"Acme Cosmetics"}},"description":"Brand the campaign is associated with."},"CampaignStats":{"required":["activated_creators","creators","posts"],"type":"object","properties":{"posts":{"type":"integer","description":"Posts attributed to the campaign.","format":"int64","example":42},"creators":{"type":"integer","description":"Creators assigned to the campaign.","format":"int64","example":10},"activated_creators":{"type":"integer","description":"Creators with at least one post attributed to the campaign.","format":"int64","example":8},"response_rate":{"type":"number","description":"`activated_creators` divided by `creators`, as a fraction (e.g. `0.8` for 80%). `null` when the campaign has no creators.","example":0.8},"engagements":{"type":"integer","description":"Total engagements across the campaign's posts. `null` when no post carries engagement data, which is distinct from a measured `0`.","format":"int64","example":12450},"video_views":{"type":"integer","description":"Total video views across the campaign's posts. `null` when no post carries video view data, which is distinct from a measured `0`.","format":"int64","example":850000},"engagement_rate":{"type":"number","description":"`engagements` summed across the campaign's posts, divided by those same posts' potential reach summed, as a fraction (e.g. `0.034` for 3.4%). Only posts with both a measured engagement count and a known potential reach are included; in practice that excludes stories, ephemeral posts and live streams, which carry no measurable engagement. That set is narrower than the one behind `engagements`, so the two do not divide into each other. `null` when no post qualifies.","example":0.034}},"description":"Precomputed aggregate stats for the campaign.\n\n`null` when the campaign's stats have not been computed yet — a campaign created very recently, ahead of the warehouse's next run. When the object is present its counts are measurements, so a `0` means the campaign genuinely has none.\n\nThe two rates are `null` when they have no denominator — no creators for `response_rate`, no potential reach for `engagement_rate` — which is distinct from a rate of zero.\n\nComputed in the warehouse and refreshed on its cadence, so these can lag the campaign's live posts and creators by up to a day. A present object is not a fresh one.","nullable":true},"CampaignSummary":{"required":["created_at","currency","id","name","spend"],"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier of the campaign (project). Immutable.","format":"int32","example":136221},"name":{"type":"string","description":"Name of the campaign as set in the Traackr app. May change over time as users edit it.","example":"Spring Launch 2024"},"brand":{"$ref":"#/components/schemas/Brand"},"start_date":{"type":"string","description":"Start date of the campaign (YYYY-MM-DD). May be empty on older or draft campaigns.","format":"date","example":"2024-09-13"},"end_date":{"type":"string","description":"End date of the campaign (YYYY-MM-DD). May be empty on older or draft campaigns.","format":"date","example":"2024-12-31"},"category":{"type":"string","description":"Comma-separated list of categories assigned to the campaign in the Traackr app.","example":"Skincare"},"country_iso":{"type":"string","description":"ISO 3166-1 alpha-2 country code associated with the campaign, or a comma-separated list of codes when the campaign has several countries (e.g. `CA,US`).","example":"US"},"created_at":{"type":"string","description":"Timestamp the campaign was created in Traackr (UTC, ISO-8601).","format":"date-time","example":"2024-09-13T17:12:06Z"},"modified_at":{"type":"string","description":"Timestamp the campaign was last modified in Traackr (UTC, ISO-8601).","format":"date-time","example":"2025-02-17T20:24:10Z"},"refreshed_at":{"type":"string","description":"Timestamp when the campaign report was last refreshed (UTC, ISO-8601). Null when no report data has been generated for this campaign.","format":"date-time","example":"2025-06-01T12:00:00Z"},"stats":{"$ref":"#/components/schemas/CampaignStats"},"currency":{"type":"string","description":"ISO 4217 currency for the campaign's monetary values, e.g. `spend` (uppercase).","example":"USD"},"spend":{"type":"integer","description":"Total spend on the campaign, in `currency` minor units. `0` when no spend has been recorded. The same figure as `total_spend` on the campaign spend endpoint.","format":"int64","example":5000000}},"description":"Campaigns matching the request filters, in the order returned. Use `page` to fetch additional results."},"CampaignsResponse":{"required":["campaigns","page"],"type":"object","properties":{"campaigns":{"type":"array","description":"Campaigns matching the request filters, in the order returned. Use `page` to fetch additional results.","items":{"$ref":"#/components/schemas/CampaignSummary"}},"page":{"$ref":"#/components/schemas/PageInfo"}}},"PageInfo":{"required":["has_next","limit"],"type":"object","properties":{"next_cursor":{"type":"string","description":"Opaque pagination cursor. Pass it as the `cursor` query parameter on the next request to fetch the following page; absent when there are no more pages.","example":"MTM4MTEx"},"has_next":{"type":"boolean","description":"Whether more pages are available beyond this one.","example":true},"limit":{"type":"integer","description":"Maximum number of items per page — echoes the `limit` query parameter, or the server default when the caller omitted it.","format":"int32","example":25}},"description":"Pagination metadata for this response."},"CampaignDetail":{"required":["created_at","currency","id","instagram_story_tracking_enabled","name","objective","spend"],"type":"object","properties":{"id":{"type":"integer","description":"Unique identifier of the campaign (project). Immutable.","format":"int32","example":136221},"name":{"type":"string","description":"Name of the campaign as set in the Traackr app. May change over time as users edit it.","example":"Spring Launch 2024"},"brand":{"$ref":"#/components/schemas/Brand"},"start_date":{"type":"string","description":"Start date of the campaign (YYYY-MM-DD). May be empty on older or draft campaigns.","format":"date","example":"2024-09-13"},"end_date":{"type":"string","description":"End date of the campaign (YYYY-MM-DD). May be empty on older or draft campaigns.","format":"date","example":"2024-12-31"},"keywords":{"type":"string","description":"Keyword query expression configured on the campaign in the Traackr app. Null when no keywords are set.","example":"adidas OR @adidas"},"category":{"type":"string","description":"Comma-separated list of categories assigned to the campaign in the Traackr app.","example":"Skincare"},"objective":{"type":"string","description":"Stated objective or goal of the campaign, as set in the Traackr app. Free-form text.","example":"Drive awareness for the new product launch."},"product_line":{"type":"string","description":"Product line associated with the campaign, drawn from the account's product line catalog. Values are account-specific.","example":"Luxury"},"collaboration_type":{"type":"string","description":"Type of collaboration arrangement with creators (e.g. `Content Creation`). Free-form text set in the Traackr app.","example":"Content Creation"},"budget":{"type":"integer","description":"Planned budget for the campaign, in `currency` minor units. See also `spend`.","format":"int64","example":5000000},"currency":{"type":"string","description":"ISO 4217 currency for the campaign's monetary values, e.g. `budget` and `spend` (uppercase).","example":"USD"},"country_iso":{"type":"string","description":"Target country for the campaign, as an ISO 3166-1 alpha-2 code (two-letter uppercase, e.g. `US`, `FR`, `JP`), or a comma-separated list of codes when the campaign has several countries (e.g. `CA,US`).","example":"US"},"po_number":{"type":"string","description":"Purchase order number associated with the campaign. Null when not set.","example":"PO-2024-001"},"instagram_story_tracking_enabled":{"type":"boolean","description":"Whether Instagram Story tracking is enabled for this campaign. Defaults to false when not set at source.","example":true},"created_at":{"type":"string","description":"Timestamp the campaign was created in Traackr (UTC, ISO-8601).","format":"date-time","example":"2024-09-13T17:12:06Z"},"modified_at":{"type":"string","description":"Timestamp the campaign was last modified in Traackr (UTC, ISO-8601).","format":"date-time","example":"2025-02-17T20:24:10Z"},"refreshed_at":{"type":"string","description":"Timestamp when the campaign report was last refreshed (UTC, ISO-8601). Null when no report data has been generated for this campaign.","format":"date-time","example":"2025-06-01T12:00:00Z"},"stats":{"$ref":"#/components/schemas/CampaignStats"},"spend":{"type":"integer","description":"Total spend on the campaign, in `currency` minor units. `0` when no spend has been recorded. The same figure as `total_spend` on the campaign spend endpoint.","format":"int64","example":5000000}}},"CampaignSpend":{"required":["additional_costs","campaign_id","currency","deliverables","products","total_spend"],"type":"object","properties":{"campaign_id":{"type":"integer","description":"Unique identifier of the campaign (project). Immutable.","format":"int32","example":136221},"currency":{"type":"string","description":"ISO 4217 currency, uppercase.","example":"USD"},"total_spend":{"type":"integer","description":"deliverables.total + products.total + additional_costs.total, in minor units.","format":"int64","example":71090000},"deliverables":{"$ref":"#/components/schemas/CampaignSpendDeliverables"},"products":{"$ref":"#/components/schemas/SpendProducts"},"additional_costs":{"$ref":"#/components/schemas/SpendAdditionalCosts"}}},"CampaignSpendDeliverables":{"required":["boosted_content","content","total"],"type":"object","properties":{"total":{"type":"integer","description":"Sum of content + boosted_content, in minor units.","format":"int64","example":70050000},"content":{"type":"integer","description":"Sum of agreed_rate × number_of_posts across deliverables, in minor units.","format":"int64","example":70000000},"boosted_content":{"type":"integer","description":"Sum of boosted_cost across deliverables, in minor units.","format":"int64","example":50000}},"description":"Campaign-wide deliverable spend totals."},"SpendAdditionalCosts":{"required":["by_category","total"],"type":"object","properties":{"total":{"type":"integer","description":"Sum of by_category amounts, in minor units.","format":"int64","example":40000},"by_category":{"type":"array","description":"Per-category amounts, ordered system-first then customs.","items":{"$ref":"#/components/schemas/SpendByCategory"}}},"description":"Additional-cost total and per-category breakdown."},"SpendByCategory":{"required":["amount","category"],"type":"object","properties":{"category":{"type":"string","description":"Cost category display label.","example":"Usage Rights"},"amount":{"type":"integer","description":"Amount for this category, in minor units.","format":"int64","example":40000}},"description":"Per-category amounts, ordered system-first then customs."},"SpendProducts":{"required":["total"],"type":"object","properties":{"total":{"type":"integer","description":"Sum of cost × quantity across products, in minor units.","format":"int64","example":1000000}},"description":"Product spend total."},"CampaignPostMetrics":{"type":"object","properties":{"engagements":{"type":"integer","description":"`reactions` + `comments` + `shares` + `saves`. `null` on stories, ephemeral posts and live streams, which carry no measurable engagement.","format":"int64","example":12450},"reactions":{"type":"integer","description":"Likes and their per-platform equivalents — for example TikTok diggs, Facebook reactions, Reddit votes.","format":"int64","example":11200},"comments":{"type":"integer","description":"Comments and replies.","format":"int64","example":980},"shares":{"type":"integer","description":"Shares, reposts and retweets.","format":"int64","example":270},"saves":{"type":"integer","description":"Saves and bookmarks. Counted in `engagements`, but never in `vit`.","format":"int64","example":115},"video_views":{"type":"integer","description":"Video plays where the platform reports them, otherwise video views. Not the same figure `vit` uses for visibility.","format":"int64","example":850000},"impressions":{"type":"integer","description":"Times the post was displayed, where the platform reports it. Counts repeat displays to the same person, unlike `actualized_reach`.","format":"int64","example":1020000},"actualized_reach":{"type":"integer","description":"Distinct people the post actually reached, where the platform reports it. Measured, unlike `potential_reach`, which is an estimate.","format":"int64","example":760000},"potential_reach":{"type":"integer","description":"Estimated audience the post could reach, based on the creator's following. An estimate rather than a measurement — `actualized_reach` is the measured figure. Denominator of `engagement_rate`, `video_views_rate` and `vitality_rate`.","format":"int64","example":1200000},"engagement_rate":{"type":"number","description":"`engagements` divided by `potential_reach`, as a fraction (e.g. `0.034` for 3.4%). `null` when it cannot be computed — no potential reach, or no engagements recorded at all — and on stories, ephemeral posts and live streams. A genuine zero (engagements of 0 against positive reach) is returned as `0`.","example":0.034},"vit":{"type":"number","description":"Traackr Vitality score: a weighted blend of the post's visibility and its engagement, relative to the audience it could reach. `saves` are excluded. `null` on platforms Traackr has no vitality weights for.","example":72.5},"video_views_rate":{"type":"number","description":"`video_views` divided by `potential_reach`.","example":0.708},"vitality_rate":{"type":"number","description":"`vit` multiplied by 1000, divided by `potential_reach`.","example":0.06},"organic_engagements":{"type":"integer","description":"`reactions` + `comments` + `shares` from unpaid distribution — **without** `saves`, so `organic_engagements` + `paid_engagements` does not add up to `engagements`. Reported by Instagram, Snapchat and TikTok only. `null` on stories, ephemeral posts and live streams.","format":"int64","example":9800},"organic_video_views":{"type":"integer","description":"`video_views` from unpaid distribution: organic plays where reported, otherwise organic views.","format":"int64","example":610000},"organic_impressions":{"type":"integer","description":"`impressions` from unpaid distribution. Reported by Instagram and Snapchat only.","format":"int64","example":740000},"paid_engagements":{"type":"integer","description":"`reactions` + `comments` + `shares` from paid distribution — **without** `saves`, mirroring `organic_engagements`. `null` on stories, ephemeral posts and live streams.","format":"int64","example":2650},"paid_video_views":{"type":"integer","description":"`video_views` from paid distribution: paid plays where reported, otherwise paid views.","format":"int64","example":240000},"paid_impressions":{"type":"integer","description":"`impressions` from paid distribution. Reported by Instagram and Snapchat only.","format":"int64","example":280000}},"description":"Normalized metrics for a post, comparable across platforms.\n\nEvery field is nullable, and `null` never means zero: it means the metric was not measured — the platform does not report it for this post type, or it could not be derived. A measured zero is returned as `0`.\n\nEach field documents what it is composed of. Which raw figures a platform actually reports varies, so a total is over whatever that platform provided; `raw_metrics` carries those per-platform figures unchanged."},"CampaignPostSummary":{"required":["creator_id","metrics","platform","post_id","publication_type","raw_metrics"],"type":"object","properties":{"post_id":{"type":"string","description":"Unique identifier for the post (32-character hexadecimal value, the post UUID with dashes stripped).","example":"9b8a5d12f0254f4a92e6a7d6b6c4d1a0"},"creator_id":{"type":"string","description":"Identifier of the creator who published the post (32-character hexadecimal value).","example":"00b2c4e1706841ff88f91848ffa6625f"},"url":{"type":"string","description":"Canonical URL of the post on the originating platform.","example":"http://youtube.com/watch?v=vivSXNiUkYs"},"platform":{"type":"string","description":"Social platform the post was published on (e.g. `youtube`, `instagram`, `tiktok`, `facebook`).","example":"youtube"},"content_type":{"type":"string","description":"High-level content type captured for the post (e.g. `video`, `image`, `text`, `album`). `null` when the underlying post has not yet been ingested.","example":"video"},"publication_type":{"type":"string","description":"Publication type captured for the post (e.g. `stream`, `feed`, `story`, `clip`).","example":"feed"},"video_type":{"type":"string","description":"Platform-specific video sub-type when the post is a video (e.g. `short_form`, `long_form`).","example":"short_form"},"title":{"type":"string","description":"Title of the post when the platform exposes one (e.g. YouTube videos).","example":"Summer routine ✨"},"description":{"type":"string","description":"Caption / description body for the post."},"language":{"type":"string","description":"Detected language of the post content (ISO 639-1 lowercase code).","example":"en"},"published_at":{"type":"string","description":"Timestamp the post was published on the platform (UTC, ISO-8601).","format":"date-time","example":"2026-03-04T22:00:55Z"},"metrics":{"$ref":"#/components/schemas/CampaignPostMetrics"},"raw_metrics":{"type":"array","description":"Platform-reported metrics for the post before normalization, ordered by `name`. Empty when the originating platform reported none.","items":{"$ref":"#/components/schemas/RawMetric"}},"engagements":{"type":"number","description":"**Deprecated** — use `metrics.engagements`. This field and the other four marked deprecated here duplicate values now published under `metrics`, and are scheduled for removal.","example":12450,"deprecated":true},"video_views":{"type":"number","description":"**Deprecated** — use `metrics.video_views`.","example":850000,"deprecated":true},"potential_reach":{"type":"number","description":"**Deprecated** — use `metrics.potential_reach`.","example":1200000,"deprecated":true},"engagement_rate":{"type":"number","description":"**Deprecated** — use `metrics.engagement_rate`.","example":0.034,"deprecated":true},"vit":{"type":"number","description":"**Deprecated** — use `metrics.vit`. Kept as a JSON `double` rather than following `metrics.vit` to a decimal, so this field's notation is unchanged from before `metrics` existed.","format":"double","example":72.5,"deprecated":true}},"description":"Posts associated with the campaign, ordered by the requested `sort_by` and `sort_order`. Use `page` to fetch additional results."},"CampaignPostsResponse":{"required":["page","posts"],"type":"object","properties":{"posts":{"type":"array","description":"Posts associated with the campaign, ordered by the requested `sort_by` and `sort_order`. Use `page` to fetch additional results.","items":{"$ref":"#/components/schemas/CampaignPostSummary"}},"page":{"$ref":"#/components/schemas/PageInfo"}}},"RawMetric":{"required":["name"],"type":"object","properties":{"name":{"type":"string","description":"Name of the metric as reported by the originating platform. The set of names varies by platform and post type.","example":"likes"},"value":{"type":"number","description":"Value reported for this metric.","example":812}},"description":"A single platform-reported metric, exactly as collected, before any normalization."},"CampaignMetrics":{"required":["campaign_id","creator_count","measured_post_count","post_count"],"type":"object","properties":{"campaign_id":{"type":"integer","description":"Numeric campaign id these totals cover.","format":"int32","example":136221},"post_count":{"type":"integer","description":"Number of posts in scope — every post the filters matched, whether or not it reported any metric.","format":"int64","example":42},"creator_count":{"type":"integer","description":"Number of distinct creators with at least one post in scope. Not the campaign roster size: a contracted creator who has not posted is absent here but still present on `/creators`.","format":"int64","example":12},"measured_post_count":{"type":"integer","description":"Number of posts that entered the `engagement_rate` computation, so consumers can state coverage (\"12 of 14 posts report engagements\"). Counts the posts reporting an engagements total against a positive reach. Covers `engagement_rate` only — the other two rates have their own eligible sets, described on each.","format":"int64","example":38},"engagements":{"type":"integer","description":"Total engagements. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":14732},"reactions":{"type":"integer","description":"Total reactions (likes and equivalents). Reported even on post types that report no `engagements` total. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":12010},"comments":{"type":"integer","description":"Total comments. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":940},"shares":{"type":"integer","description":"Total shares or reposts. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":1520},"saves":{"type":"integer","description":"Total saves or bookmarks. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":262},"video_views":{"type":"integer","description":"Total video views. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":210400},"impressions":{"type":"integer","description":"Total actualized impressions, where the platform reports them. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":310500},"actualized_reach":{"type":"integer","description":"Total actualized reach, where the platform reports it. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":184000},"potential_reach":{"type":"integer","description":"Total estimated potential reach. NOT the denominator of the rate fields (`engagement_rate`, `video_views_rate`, `vitality_rate`): each divides by the reach of only the posts eligible for that rate, which is a subset of this sum. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. Two posts seen by the same person count twice. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":500000},"engagement_rate":{"type":"number","description":"Σ`engagements` ÷ Σ`potential_reach`, as a fraction (`0.034` for 3.4%). Recomputed as a ratio of sums, never an average of per-post rates. Summed only over posts where both sides are present and reach is positive — upstream nulls the per-post rate when reach is missing or non-positive, so including those posts would add to the numerator without adding to the denominator. Consequence: this rate cannot be reproduced from the published sums, which cover all posts rather than the eligible ones. `null` when no post qualifies, never `0`.","example":0.0295},"vit":{"type":"number","description":"Total Traackr Vitality (VIT) score — additive, not a rate. Posts on platforms without VIT reference weights contribute nothing, and neither do posts whose score is not a finite number — those are excluded from `vitality_rate`'s denominator too. Summed across the posts in scope that do contribute; cumulative, never de-duplicated. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","example":12.4},"video_views_rate":{"type":"number","description":"Σ`video_views` ÷ Σ`potential_reach`. Recomputed as a ratio of sums, never an average of per-post rates. Summed only over posts where both sides are present and reach is positive — upstream nulls the per-post rate when reach is missing or non-positive, so including those posts would add to the numerator without adding to the denominator. Consequence: this rate cannot be reproduced from the published sums, which cover all posts rather than the eligible ones. `null` when no post qualifies, never `0`.","example":0.4208},"vitality_rate":{"type":"number","description":"Σ`vit` × 1000 ÷ Σ`potential_reach`. Recomputed as a ratio of sums, never an average of per-post rates. Summed only over posts where both sides are present and reach is positive — upstream nulls the per-post rate when reach is missing or non-positive, so including those posts would add to the numerator without adding to the denominator. Consequence: this rate cannot be reproduced from the published sums, which cover all posts rather than the eligible ones. `null` when no post qualifies, never `0`.","example":0.0248},"organic_engagements":{"type":"integer","description":"Engagements attributed to organic distribution. Counts reactions, comments and shares only — NOT saves — so `organic_engagements` + `paid_engagements` deliberately does not equal `engagements`. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":9800},"organic_video_views":{"type":"integer","description":"Video views attributed to organic distribution. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":130000},"organic_impressions":{"type":"integer","description":"Impressions attributed to organic distribution. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":96000},"paid_engagements":{"type":"integer","description":"Engagements attributed to paid distribution. Counts reactions, comments and shares only — NOT saves — mirroring `organic_engagements`. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":4932},"paid_video_views":{"type":"integer","description":"Video views attributed to paid distribution. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":80400},"paid_impressions":{"type":"integer","description":"Impressions attributed to paid distribution. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":42000}},"description":"Campaign-wide aggregation of the per-post normalized metrics, computed live over the posts in scope. Counters are sums; rates are recomputed from the summed parts. Reach is cumulative across posts, so it is not a unique-audience figure."},"CampaignCreatorSummary":{"required":["creator_id"],"type":"object","properties":{"creator_id":{"type":"string","description":"Unique identifier for the creator profile in Traackr (32-character hexadecimal value).","example":"00b2c4e1706841ff88f91848ffa6625f"},"name":{"type":"string","description":"Creator's name as it appears on their Traackr profile (the overall profile name, not their per-platform handle).","example":"Alice Martin"},"description":{"type":"string","description":"Free-text description on the creator's Traackr profile.","example":"Beauty creator focused on clean cosmetics and skincare routines."},"location":{"$ref":"#/components/schemas/CreatorLocation"},"audience":{"type":"integer","description":"Total audience across the creator's tracked channels: sum of platform follower / subscriber counts.","format":"int64","example":56995}},"description":"Creators participating in the campaign, in the order returned. Use `page` to fetch additional results."},"CampaignCreatorsResponse":{"required":["creators","page"],"type":"object","properties":{"creators":{"type":"array","description":"Creators participating in the campaign, in the order returned. Use `page` to fetch additional results.","items":{"$ref":"#/components/schemas/CampaignCreatorSummary"}},"page":{"$ref":"#/components/schemas/PageInfo"}}},"CreatorSpend":{"required":["additional_costs","creator_id","currency","deliverables","products","total_spend"],"type":"object","properties":{"creator_id":{"type":"string","description":"Unique identifier for the creator profile in Traackr (32-character hexadecimal value).","example":"00b2c4e1706841ff88f91848ffa6625f"},"currency":{"type":"string","description":"ISO 4217 currency, uppercase.","example":"USD"},"total_spend":{"type":"integer","description":"deliverables.total + products.total + additional_costs.total, in minor units.","format":"int64","example":70590000},"deliverables":{"$ref":"#/components/schemas/CreatorSpendDeliverables"},"products":{"$ref":"#/components/schemas/SpendProducts"},"additional_costs":{"$ref":"#/components/schemas/SpendAdditionalCosts"}}},"CreatorSpendDeliverables":{"required":["boosted_content","by_platform","content","total"],"type":"object","properties":{"total":{"type":"integer","description":"Sum of content + boosted_content, in minor units.","format":"int64","example":70050000},"content":{"type":"integer","description":"Sum of agreed_rate × number_of_posts across deliverables, in minor units.","format":"int64","example":70000000},"boosted_content":{"type":"integer","description":"Sum of boosted_cost across deliverables, in minor units.","format":"int64","example":50000},"by_platform":{"type":"array","description":"Per-platform content-cost rollup.","items":{"$ref":"#/components/schemas/SpendDeliverablesByPlatform"}}},"description":"Deliverable spend totals, including the per-platform rollup."},"SpendDeliverablesByPlatform":{"required":["contracted_posts","platform","total"],"type":"object","properties":{"platform":{"type":"string","description":"Platform, as a lowercase token (e.g. `instagram`, `tiktok`, `youtube`).","example":"instagram"},"total":{"type":"integer","description":"Content cost on this platform, in minor units. Excludes boosted content.","format":"int64","example":20000000},"contracted_posts":{"type":"integer","description":"Sum of contracted posts for the creator's deliverables on this platform.","format":"int32","example":2}},"description":"Per-platform content-cost rollup."},"CampaignCreatorProductsResponse":{"required":["page","products"],"type":"object","properties":{"products":{"type":"array","description":"Product line items, ordered by created_at. Use `page` to fetch more.","items":{"$ref":"#/components/schemas/Product"}},"page":{"$ref":"#/components/schemas/PageInfo"}}},"Product":{"required":["cost","created_at","currency","name","quantity","shipping_status","subtotal"],"type":"object","properties":{"name":{"type":"string","description":"Product name.","example":"GIFT 3"},"currency":{"type":"string","description":"ISO 4217 currency, uppercase.","example":"USD"},"cost":{"type":"integer","description":"Per-unit cost, in minor units.","format":"int64","example":500000},"quantity":{"type":"integer","description":"Number of units.","format":"int32","example":1},"subtotal":{"type":"integer","description":"cost × quantity, in minor units.","format":"int64","example":500000},"shipping_status":{"type":"string","description":"Shipping state.","example":"sent"},"created_at":{"type":"string","description":"Timestamp the product was created in Traackr (UTC, ISO-8601).","format":"date-time","example":"2026-05-01T10:00:00Z"},"modified_at":{"type":"string","description":"Timestamp the product was last modified in Traackr (UTC, ISO-8601).","format":"date-time","example":"2026-05-08T14:00:00Z"}},"description":"Product line items, ordered by created_at. Use `page` to fetch more."},"CreatorMetrics":{"required":["creator_id","measured_post_count","post_count"],"type":"object","properties":{"creator_id":{"type":"string","description":"Creator profile id (32-character hexadecimal value) these totals cover.","example":"00b2c4e1706841ff88f91848ffa6625f"},"post_count":{"type":"integer","description":"Number of posts in scope — every post the filters matched, whether or not it reported any metric.","format":"int64","example":42},"measured_post_count":{"type":"integer","description":"Number of posts that entered the `engagement_rate` computation, so consumers can state coverage (\"12 of 14 posts report engagements\"). Counts the posts reporting an engagements total against a positive reach. Covers `engagement_rate` only — the other two rates have their own eligible sets, described on each.","format":"int64","example":38},"engagements":{"type":"integer","description":"Total engagements. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":14732},"reactions":{"type":"integer","description":"Total reactions (likes and equivalents). Reported even on post types that report no `engagements` total. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":12010},"comments":{"type":"integer","description":"Total comments. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":940},"shares":{"type":"integer","description":"Total shares or reposts. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":1520},"saves":{"type":"integer","description":"Total saves or bookmarks. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":262},"video_views":{"type":"integer","description":"Total video views. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":210400},"impressions":{"type":"integer","description":"Total actualized impressions, where the platform reports them. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":310500},"actualized_reach":{"type":"integer","description":"Total actualized reach, where the platform reports it. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":184000},"potential_reach":{"type":"integer","description":"Total estimated potential reach. NOT the denominator of the rate fields (`engagement_rate`, `video_views_rate`, `vitality_rate`): each divides by the reach of only the posts eligible for that rate, which is a subset of this sum. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. Two posts seen by the same person count twice. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":500000},"engagement_rate":{"type":"number","description":"Σ`engagements` ÷ Σ`potential_reach`, as a fraction (`0.034` for 3.4%). Recomputed as a ratio of sums, never an average of per-post rates. Summed only over posts where both sides are present and reach is positive — upstream nulls the per-post rate when reach is missing or non-positive, so including those posts would add to the numerator without adding to the denominator. Consequence: this rate cannot be reproduced from the published sums, which cover all posts rather than the eligible ones. `null` when no post qualifies, never `0`.","example":0.0295},"vit":{"type":"number","description":"Total Traackr Vitality (VIT) score — additive, not a rate. Posts on platforms without VIT reference weights contribute nothing, and neither do posts whose score is not a finite number — those are excluded from `vitality_rate`'s denominator too. Summed across the posts in scope that do contribute; cumulative, never de-duplicated. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","example":12.4},"video_views_rate":{"type":"number","description":"Σ`video_views` ÷ Σ`potential_reach`. Recomputed as a ratio of sums, never an average of per-post rates. Summed only over posts where both sides are present and reach is positive — upstream nulls the per-post rate when reach is missing or non-positive, so including those posts would add to the numerator without adding to the denominator. Consequence: this rate cannot be reproduced from the published sums, which cover all posts rather than the eligible ones. `null` when no post qualifies, never `0`.","example":0.4208},"vitality_rate":{"type":"number","description":"Σ`vit` × 1000 ÷ Σ`potential_reach`. Recomputed as a ratio of sums, never an average of per-post rates. Summed only over posts where both sides are present and reach is positive — upstream nulls the per-post rate when reach is missing or non-positive, so including those posts would add to the numerator without adding to the denominator. Consequence: this rate cannot be reproduced from the published sums, which cover all posts rather than the eligible ones. `null` when no post qualifies, never `0`.","example":0.0248},"organic_engagements":{"type":"integer","description":"Engagements attributed to organic distribution. Counts reactions, comments and shares only — NOT saves — so `organic_engagements` + `paid_engagements` deliberately does not equal `engagements`. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":9800},"organic_video_views":{"type":"integer","description":"Video views attributed to organic distribution. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":130000},"organic_impressions":{"type":"integer","description":"Impressions attributed to organic distribution. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":96000},"paid_engagements":{"type":"integer","description":"Engagements attributed to paid distribution. Counts reactions, comments and shares only — NOT saves — mirroring `organic_engagements`. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":4932},"paid_video_views":{"type":"integer","description":"Video views attributed to paid distribution. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":80400},"paid_impressions":{"type":"integer","description":"Impressions attributed to paid distribution. Summed across every post in scope — cumulative, never de-duplicated to a unique audience. `null`, not `0`, when at least one post is in scope but none reported it: upstream distinguishes a measured zero from nothing measured, and this endpoint preserves that. When no post is in scope at all (`post_count` is `0`) every counter is `0` instead, so `post_count` is what tells \"no posts\" from \"posts that report nothing\".","format":"int64","example":42000}},"description":"One creator's aggregation of the per-post normalized metrics on the campaign, computed live over that creator's posts in scope. Identical in value to this creator's row on `/creators/metrics` for the same filters. A creator on the campaign with no posts in scope is returned with zero counts and null rates rather than omitted or 404 — \"not on the campaign\" and \"nothing to report\" are different answers."},"CampaignCreatorDeliverablesResponse":{"required":["deliverables","page"],"type":"object","properties":{"deliverables":{"type":"array","description":"Deliverable line items, ordered by created_at. Use `page` to fetch more.","items":{"$ref":"#/components/schemas/Deliverable"}},"page":{"$ref":"#/components/schemas/PageInfo"}}},"Deliverable":{"required":["agreed_rate","boosted_cost","content_cost","created_at","currency","media_type","number_of_posts","platform","usage_rights"],"type":"object","properties":{"platform":{"type":"string","description":"Platform, as a lowercase token (e.g. `instagram`, `tiktok`, `youtube`).","example":"instagram"},"media_type":{"type":"string","description":"Media type (e.g. `video`, `photo`, `story`).","example":"video"},"currency":{"type":"string","description":"ISO 4217 currency, uppercase.","example":"USD"},"agreed_rate":{"type":"integer","description":"Agreed/contracted per-post cost, in minor units.","format":"int64","example":20000000},"proposed_rate":{"type":"integer","description":"Creator's proposed rate during negotiation, in minor units. Reference only.","format":"int64","example":18000000},"number_of_posts":{"type":"integer","description":"Number of contracted posts for this deliverable.","format":"int32","example":1},"content_cost":{"type":"integer","description":"agreed_rate × number_of_posts, in minor units.","format":"int64","example":20000000},"boosted_cost":{"type":"integer","description":"Boosted spend for the whole deliverable (not per-post), in minor units.","format":"int64","example":0},"boosted_on":{"type":"string","description":"Date the deliverable was boosted, if any.","format":"date"},"usage_rights":{"type":"boolean","description":"Whether usage rights are included for this deliverable.","example":false},"review_by":{"type":"string","description":"Date the deliverable is due for review, if set.","format":"date"},"publish_by":{"type":"string","description":"Date the deliverable is due to publish, if set.","format":"date"},"created_at":{"type":"string","description":"Timestamp the deliverable was created in Traackr (UTC, ISO-8601).","format":"date-time","example":"2026-05-01T10:00:00Z"},"modified_at":{"type":"string","description":"Timestamp the deliverable was last modified in Traackr (UTC, ISO-8601).","format":"date-time","example":"2026-05-08T14:00:00Z"}},"description":"Deliverable line items, ordered by created_at. Use `page` to fetch more."},"CampaignCreatorsSpendResponse":{"required":["creators","page"],"type":"object","properties":{"creators":{"type":"array","description":"Per-creator spend rows, in the order returned. Use `page` to fetch more.","items":{"$ref":"#/components/schemas/CreatorSpend"}},"page":{"$ref":"#/components/schemas/PageInfo"}}},"CampaignCreatorsMetricsResponse":{"required":["creators","page"],"type":"object","properties":{"creators":{"type":"array","description":"Per-creator aggregation rows, one per creator with at least one post matching the filters — a creator on the campaign who has not posted is not a row here. Ordered by the requested `sort_by` and `sort_order`, with creators whose sort metric is null last in both directions. Use `page` to fetch additional results.","items":{"$ref":"#/components/schemas/CreatorMetrics"}},"page":{"$ref":"#/components/schemas/PageInfo"}}}},"securitySchemes":{"ApiKey":{"type":"apiKey","description":"Traackr-issued API key, bound to a customer account. Required on every request.","name":"X-Api-Key","in":"header"}}}}