Traffic Reports API (read-only)
GET /api/v2/traffic_reports
Returns the GA4 traffic that MetrixNest synced for your product, summed over a date range and grouped by source, medium, campaign, and landing_page.
Authentication
This endpoint uses a Reports API key, not an Events API key:
bash
Authorization: Bearer <YOUR_REPORTS_API_KEY>
The two key kinds are separate. A Reports API key is rejected (401) by the ingest endpoints, and an Events API key is rejected (401) here. A Reports API key reads data for one product only.
Query parameters
| Name | Required | Description |
|---|---|---|
from |
yes | First day of the range, YYYY-MM-DD, inclusive |
to |
yes | Last day of the range, YYYY-MM-DD, inclusive. Must be on or after from |
source |
no | Exact match on source (for example saashub.com) |
medium |
no | Exact match on medium (for example referral) |
The range can hold at most 366 days, counting both from and to.
Example
bash
curl "/api/v2/traffic_reports?from=2026-09-01&to=2026-09-30&medium=referral" \
-H "Authorization: Bearer YOUR_REPORTS_API_KEY"
json
{
"product": "0b0f6c1e-6f0b-4a53-9f0e-2f5f5a0f7c11",
"from": "2026-09-01",
"to": "2026-09-30",
"connection": { "provider": "ga4", "status": "active", "last_synced_at": "2026-10-01T03:00:12Z" },
"coverage": { "first_report_date": "2026-09-01", "last_report_date": "2026-09-30" },
"rows": [
{
"source": "saashub.com",
"medium": "referral",
"campaign": "(not set)",
"landing_page": "/",
"sessions": 12,
"users": 10,
"page_views": 30,
"conversions": 1
}
],
"totals": { "sessions": 12, "users": 10, "page_views": 30, "conversions": 1 },
"truncated": false
}
Response fields
product: UUID of the product the key belongs to.connection: the product's GA4 data connection, ornullwhen the product has none.statusis one ofpending,active,syncing,error,disconnected.coverage: the earliest and latestreport_datestored for the product inside the requested range. Both arenullwhen nothing was synced for that window.sourceandmediumfilters do not changecoverage, so you can tell "no data synced" (coverageisnull) from "synced, but no matching sessions" (coverageis set,rowsis empty).rows: ordered bysessionsdescending, thensource.campaignandlanding_pagecan benull.totals: sums over every matching row, including rows cut off by the row limit.truncated:truewhen more than 5000 groups matched. Only the first 5000 are inrows; narrow the range or add a filter.
Errors
| Status | error.code |
When |
|---|---|---|
400 |
parameter_missing |
from or to is absent |
400 |
invalid_date |
from or to is not a YYYY-MM-DD date |
400 |
invalid_date_range |
to is before from, or the range is longer than 366 days |
400 |
invalid_filter |
source or medium is not a plain string |
401 |
unauthorized |
Missing, wrong-kind, revoked, or expired key |
429 |
rate_limited |
More than 60 requests per minute per key or per IP |