PricingCreators
Get started ↗Sign in
Sign inGet started ↗Sign up with Google
  • Getting Started
  • Campaigns
    • How to Make a Campaign
    • How to Make a Slideshow Playbook
    • How to Make a UGC Playbook
    • Slideshow Content Ideas
    • UGC Content Ideas
    • How to Give Premium App Access to Creators
  • Understanding Your Metrics
  • App Store Integrations
  • Ad Integrations
  • Reporting API
  • Connect an AI Client (MCP)
  • Team & Roles

Made with ❤️ in United StatesUnited States and HondurasHonduras
Sign in →

© Copyright 2026 Noise. All Rights Reserved.

The Noise platform
  • Free to get started
  • Creator marketplace
  • Creator management
  • Organic-to-paid ads
  • Campaign analytics
  • Noise Agent MCP
Noise Agent
  • Noise Agent MCP
  • Connect your AI platform
  • Documentation
Resources
  • How it works
  • Pricing
  • Documentation
  • FAQ
  • Podcasts
  • For AI (llms.txt)
Noise
  • Talk to Noise
  • Get started
  • Terms of Service
  • Privacy Policy
  • Advertiser Disclosure

Reporting API

Pull your Noise campaign performance into your own tools, review the videos creators send you, and run them as ads: daily spend, views, posts, creators, CPM, install estimates, submissions and paid ads.

The Noise Reporting API gives your brand programmatic access to daily campaign performance, to the videos creators send you for ads, and to the ads you run from them on Meta and TikTok. Base URL:

https://reporting.getnoise.com

Authentication

Every request needs two headers:

  • x-noise-org: your organization slug
  • x-noise-token: your API key

Both live in your Noise dashboard: on the new dashboard under Settings → Integrations, or on the classic dashboard under Resources → Reporting API. Keep the token secret; rotate it from the same page if it leaks.

The token also authorizes the write calls below (rejecting a submission, creating and switching ads), which spend your ad budget. Treat it like a payment credential: store it in a secret manager, never in a browser, and rotate it at once if it leaks. Writes are limited to 10 per minute per brand, and ad creation is capped per day.

Building with an AI assistant?

Give it the OpenAPI spec and it can write correct requests and generate a client for you:

https://reporting.getnoise.com/openapi.yaml

Metrics

  • spend: your cost, USD
  • views: views earned that day (daily incremental)
  • posts: approved posts
  • creators: unique creators
  • cpm: cost per 1,000 views (spend / views × 1000)
  • estimated_installs: app installs. When your brand has a connected App Store or Google Play integration, this is first-party store data for that platform; days the store data does not cover (including history from before you connected) are filled with a third-party SensorTower estimate when one is available. totals.installs_source tells you which contributed (store, estimated, or mixed). Org-wide per day, so it is only returned when grouping by day alone (not by campaign or playbook), and only if your brand has an installs source.
  • installs_by_source: an object holding each data source's raw reported installs. app_store and play_store are the first-party reports from your connected store integrations; noise is Noise's own install estimate (an overall figure by nature, never split per platform). These are independent series — a day can carry both a store report and an estimate — so they deliberately do not sum to estimated_installs, which is the single blended best-available number. A source missing from the object reported nothing for that day (or, in totals, nothing for the period); an empty object means no source reported at all. Appears wherever estimated_installs appears, on both rows and totals. More sources may be added over time; existing keys stay stable.

Group by

date (always), plus optional campaign and/or playbook (playbook_id: 0 = Custom Posts).

GET /v1/reports/daily

Daily metrics with a period totals summary.

Parameters: start_date, end_date (required, YYYY-MM-DD, UTC, up to 366 days), group_by (csv of campaign,playbook), campaign_id, playbook_id, and metrics (subset; default all).

curl -s https://reporting.getnoise.com/v1/reports/daily \
  -H "x-noise-org: your-org-slug" \
  -H "x-noise-token: $NOISE_API_KEY" \
  --get \
  --data-urlencode "start_date=2026-06-01" \
  --data-urlencode "end_date=2026-06-30"

Response:

{
  "meta": {
    "start_date": "2026-06-01",
    "end_date": "2026-06-30",
    "group_by": ["date"],
    "timezone": "UTC",
    "row_count": 30,
    "data_freshness": "~5 minutes"
  },
  "totals": {
    "spend": 2460.50, "views": 722444, "posts": 62, "creators": 51, "cpm": 3.41,
    "estimated_installs": 4010,
    "installs_source": "mixed",
    "installs_by_source": { "app_store": 2380, "play_store": 1180, "noise": 4310 }
  },
  "data": [
    { "date": "2026-06-01",
      "spend": 92.40, "views": 27110, "posts": 3, "creators": 3, "cpm": 3.41,
      "estimated_installs": 120,
      "installs_by_source": { "app_store": 80, "play_store": 40, "noise": 150 } }
  ]
}

Add group_by=campaign (or playbook) and each row gains campaign_id + campaign_name (playbook_id + playbook_name), with one row per campaign per day. Installs are org-wide rather than per campaign, so estimated_installs and installs_by_source come back on day-grain requests only — they are absent from grouped responses.

Reading the installs fields: on June 1 both stores reported (80 + 40), so estimated_installs is 120 — the store numbers. The noise value of 150 is our estimate for that same day, reported alongside rather than instead: use it to compare our estimate against your actuals, or to cover days your store data does not reach. Across the period the two store series sum to 3,560 while estimated_installs totals 4,010, because estimates filled the days the stores did not cover — that is what installs_source: "mixed" signals.

GET /v1/posts

Per-post drilldown: posts created in the date range, with lifetime stats, views gained inside the range, playbook/campaign attribution, and per-post spend. Server-side sort and pagination.

Parameters: start_date, end_date (required, YYYY-MM-DD, UTC, up to 366 days), mode (created = posts created in range, default; active = posts that gained views in range regardless of age), campaign_id (comma-separated ids, posts from any of them; omitted = all campaigns excluding posts attached to no campaign; include 0 for those unattributed posts), playbook_id (comma-separated ids; 0 = Custom Posts, combinable), page (default 1), page_size (default 25, max 100), sort (posted default, or views, range_views, likes, comments, bookmarks, shares, spend, engagement, handle), order (asc or desc).

curl -s https://reporting.getnoise.com/v1/posts \
  -H "x-noise-org: your-org-slug" \
  -H "x-noise-token: $NOISE_API_KEY" \
  --get \
  --data-urlencode "start_date=2026-06-01" \
  --data-urlencode "end_date=2026-06-30" \
  --data-urlencode "campaign_id=812" \
  --data-urlencode "sort=views"

Response:

{
  "meta": {
    "start_date": "2026-06-01", "end_date": "2026-06-30", "timezone": "UTC",
    "page": 1, "page_size": 25, "total": 1204, "row_count": 25,
    "sort": "views", "order": "desc", "data_freshness": "~5 minutes"
  },
  "data": [
    { "content_id": 991234, "user_id": 55021, "campaign_id": 812,
      "playbook_id": 3401, "playbook_name": "Stuck In Bed H+D",
      "platform": "tiktok", "handle": "kaylareset", "code": "kayla",
      "avatar_url": null, "posted_at": "2026-06-15T05:02:11Z",
      "range_views": 285848, "views": 1112010, "likes": 12000,
      "comments": 41, "bookmarks": 1300, "shares": 210,
      "engagement_rate": 0.0122, "spend": 750.0,
      "image": "https://…", "url": "https://www.tiktok.com/@kaylareset/video/…" }
  ]
}

Stats are lifetime except range_views (views gained inside the requested range). posted_at is ISO 8601 UTC — render it in your own timezone. engagement_rate = (likes + comments + bookmarks + shares) ÷ views, lifetime basis, as a 0–1 fraction.

GET /v1/campaigns and GET /v1/playbooks

List your campaigns / playbooks (id, name, active) to use as campaign_id / playbook_id filters.

curl -s https://reporting.getnoise.com/v1/campaigns \
  -H "x-noise-org: your-org-slug" \
  -H "x-noise-token: $NOISE_API_KEY"

Direct submissions

Creators can send your brand a video (or image) without posting it, for you to run as an ad. Each submission has a derived status: open (waiting for you), rejected (you rejected it; reopen with unreject), or sent (you made an ad of it). There is no approve call: creating the ad is the approval.

GET /v1/submissions

Parameters: status (open default, rejected, sent, all) and limit (default 50, max 200). open lists oldest first; the others newest first. Every row carries media_url, a signed download link for the file that is valid for an hour (meta.media_url_ttl_seconds); request the list again for a fresh one.

curl -s https://reporting.getnoise.com/v1/submissions \
  -H "x-noise-org: your-org-slug" \
  -H "x-noise-token: $NOISE_API_KEY"

Response:

{
  "meta": { "status": "open", "limit": 50, "row_count": 2, "media_url_ttl_seconds": 3600 },
  "data": [
    { "content_id": 991300, "status": "open", "media_type": "video",
      "title": "Morning routine", "notes": null, "duration_seconds": 18.4,
      "byte_size": 20480000, "original_filename": "routine.mp4", "staff_upload": false,
      "submitted_at": "2026-10-20T14:02:11Z", "campaign_id": 812,
      "thumbnail_url": "https://…", "creator": { "id": 55021, "name": "Kayla", "code": "kayla", "avatar_url": null },
      "media_url": "https://…signed…", "media_url_expires_at": "2026-10-30T16:00:00Z",
      "rejection": null, "placements": [] }
  ]
}

GET /v1/submissions/{content_id} returns one submission with a fresh link.

POST /v1/submissions/{content_id}/reject

Body (optional): reason (aesthetic, brief, or revisions) and message (your note to the creator, kept for revisions only; they can upload a new version). No money moves. Returns the submission. Conflicts: 409 already_sent when you already made an ad of it, 409 already_rejected.

curl -s -X POST https://reporting.getnoise.com/v1/submissions/991300/reject \
  -H "x-noise-org: your-org-slug" \
  -H "x-noise-token: $NOISE_API_KEY" \
  -H "content-type: application/json" \
  -d '{ "reason": "revisions", "message": "Show the app screen in the first 3 seconds." }'

POST /v1/submissions/{content_id}/unreject reopens a rejected submission (409 not_rejected otherwise).

Paid ads

A "placement" is one post or submission pushed to one ad network. Each has an ad_status: queued and processing while the creative renders and uploads; available once it sits in the ad library with no ad yet; active when a matched ad is delivering; paused when none is; not_created when the ad could not be built (last_error says why, usually a permission in your ad account); error when the push failed.

GET /v1/ads/destinations

Where an ad can be built: per connected network, the campaigns and the active or paused ad sets (Meta) or ad groups (TikTok) from Noise's mirror of your account, which refreshes every 4 hours. A paused ad set is a valid destination; the ad starts when you switch the ad set on. Add refresh=true to pull your Meta account first (at most once every 30 seconds; TikTok has no on-demand pull yet); refreshed.meta_ads says whether the pull ran.

curl -s "https://reporting.getnoise.com/v1/ads/destinations?refresh=true" \
  -H "x-noise-org: your-org-slug" \
  -H "x-noise-token: $NOISE_API_KEY"

POST /v1/ads

Body: content_ids (post ids from /v1/posts or submission ids from /v1/submissions, max 50), optional providers (meta_ads, tiktok_ads; default every connected network), destinations (an ad set id per network, from /v1/ads/destinations), ad_name (the first field of your naming convention), disclaimer_text, disclaimer_position, disclaimer_color, and paused. Answers 202 with the placements created and the posts that could not be pushed (failures[].reason). Without destinations the creative only lands in the ad library; with one, the worker also builds the ad there, active unless paused is true.

curl -s -X POST https://reporting.getnoise.com/v1/ads \
  -H "x-noise-org: your-org-slug" \
  -H "x-noise-token: $NOISE_API_KEY" \
  -H "content-type: application/json" \
  -d '{ "content_ids": [991300], "destinations": { "meta_ads": "120210000000000" }, "ad_name": "Morning routine" }'

GET /v1/ads and GET /v1/ads/{placement_id}

Your placements, newest first (limit, content_id), each with its ad_status, last_error, the ads built from it (ads[], one per ad set, with the network's status and delivery status), and lifetime totals: ad_spend_usd (the network's spend tracked against the creative), fees_paid_usd (what you paid Noise), impressions, clicks, ctr, primary_action, cpa_usd. Poll the single-placement call after creating an ad.

POST /v1/ads/{placement_id}/publish

Build an ad from a placement that is available (or not_created once you fixed the cause, or any pushed placement to run in one more ad set). Body: adset_id (required), optional ad_name, allow_existing, paused. The ad set must already hold one ad: page, text, link and call to action are copied from it (400 no_sibling otherwise). 409 already_in_adset when one of this placement's ads already runs there, unless allow_existing is true. 409 not_pushed while the creative is still uploading.

PATCH /v1/ads/{placement_id}/ads/{ad_id}

Body: { "active": true } to start delivery (spend from that ad set's budget), false to pause. ad_id is one of the placement's ads[].ad_id.

Launch an ad, end to end

  1. GET /v1/submissions and download media_url to review the video.
  2. GET /v1/ads/destinations and pick an ad set (adsets[].id) under the campaign you want.
  3. POST /v1/ads with the submission's content_id and destinations.
  4. Poll GET /v1/ads/{placement_id} until ad_status is active (usually a few minutes). not_created or error carries the reason in last_error; fix it in your ad account, then POST /v1/ads/{placement_id}/publish into the same ad set.
  5. Spend and results appear in /v1/ads and /v1/ads/stats after the next sync.

GET /v1/ads/stats

Daily ad metrics from your connected ad accounts: impressions, clicks, spend (the network's ad spend, account currency), reach, actions, installs, actions_by_type, action_values_by_type, ctr, cpm, with a period totals (plus distinct ads and creators). Parameters: start_date, end_date (required, up to 366 days), group_by (csv of provider, campaign, adset, ad), provider, campaign_id, adset_id, ad_id (csv, max 100 each), and scope (noise = the ads Noise built, default; account = every ad in the account). Grouped rows carry the entity id and its name from the mirror. Refreshed every 4 hours.

curl -s https://reporting.getnoise.com/v1/ads/stats \
  -H "x-noise-org: your-org-slug" \
  -H "x-noise-token: $NOISE_API_KEY" \
  --get \
  --data-urlencode "start_date=2026-10-01" \
  --data-urlencode "end_date=2026-10-31" \
  --data-urlencode "group_by=provider,adset"

Notes

  • Days are UTC; data lags real time by roughly 5 minutes.
  • cpm aggregates (total spend ÷ total views), so daily and campaign-level CPM can differ. This is expected.
  • Spend and views are dated independently (you're billed as views land), so daily CPM is noisier than range or campaign CPM.
  • Rate limited per org; responses include X-RateLimit-* headers. Reads allow 60 per minute, writes 10 per minute.
  • Ad metrics (/v1/ads, /v1/ads/stats) refresh every 4 hours; names of campaigns and ad sets come from Noise's mirror of your account and are null until its next sync.

Connect an AI client (MCP)

Prefer talking to your data instead of calling endpoints? Connect Claude or another MCP-capable client to https://reporting.getnoise.com/mcp from the dashboard's Integrations page and ask questions in plain language.

  1. Authentication
    1. Building with an AI assistant?
    2. Metrics
    3. Group by
    4. GET /v1/reports/daily
    5. GET /v1/posts
    6. GET /v1/campaigns and GET /v1/playbooks
    7. Direct submissions
    8. Paid ads
    9. Notes
    10. Connect an AI client (MCP)