API
API
Read your numbers from scripts, BI tools and your own agents. The API answers exactly what the dashboard shows, because both use the same query layer. With the right key, you can also send events and manage alerts, goals, triggers and URL parameters.
Guides: Keys and authentication · Query cookbook · Limits and conventions · TypeScript SDK · API reference, with a real example response for every endpoint.
Keys
Create a key in Site settings → API and AI access. It's shown once, so copy it then; revoke keys on the same page. More on keys.
- Send it as a Bearer token:
Authorization: Bearer pk_live_…. - One key works on one site, and only that site.
- Every key can read. Tick Can send events for
POST /v1/events, and Can manage for the endpoints under Managing your site. - Rate limit: 120 requests a minute per key. Responses carry
RateLimit-Limit,RateLimit-RemainingandRateLimit-Reset; a 429 includesRetry-After. See Limits and conventions.
Your first request
curl -s https://app.punchlineanalytics.com/v1/query \
-H "Authorization: Bearer $PUNCHLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"site_id": "<your site id>",
"date_range": { "from": "2026-09-01", "to": "2026-09-30" },
"metrics": ["sessions", "agent_share"],
"dimensions": ["day"]
}'
The response echoes the query as it ran (with defaults filled in), then results and meta:
{
"query": { "site_id": "…", "metrics": ["sessions", "agent_share"], "dimensions": ["day"], "confidence": "likely", "…": "…" },
"results": [{ "day": "2026-09-01", "sessions": 412, "agent_share": 0.31 }],
"meta": { "audience": "external", "min_cohort": 5, "suppressed_rows": 0, "truncated": false, "rollups_refreshed_at": "…" }
}
The query
POST /v1/query does almost everything: pick metrics, group by dimensions, narrow with filters.
| Metrics | |
|---|---|
sessions, pageviews |
Everyone |
human_sessions, agent_sessions |
Split by visitor type (agents include crawlers) |
agent_share |
agent_sessions / sessions |
thin_pageviews |
Successful responses under 2 KB (possibly JavaScript-only) |
events, event_sessions, event_value |
Custom events (with the event_name dimension) |
conversions, conversion_rate |
Goal conversions (with the goal dimension; path is the landing page) |
visitors |
Distinct visitors per day, summed over the range |
bounces, bounce_rate, avg_session_duration, pages_per_session, engaged_sessions, engagement_rate |
Session engagement (path is the landing page; also exit_path, country, device_class, browser_family, os_family) |
exits, exit_rate, avg_time_on_page |
Per-page engagement |
Dimensions (up to 4): day, week, month, path, visitor_type, actor_class, operator, tier, referrer_host, assistant, utm_source, utm_medium, utm_campaign, utm_term, utm_content, agent, agent_purpose, delivery, content_type, topic, channel, partner, exit_path, country, device_class, browser_family, os_family, plus param:<name> for each URL parameter the site allows (page and conversion metrics). See Concepts and Reports for what each value means.
Filters (up to 10) are [field, op, value], with ops eq, neq, in, not_in, starts_with, is_null, not_null. ["is_entry", "eq", true] keeps only the first page of each session (landing pages):
{
"date_range": { "from": "2026-09-01", "to": "2026-09-30" },
"metrics": ["sessions"],
"dimensions": ["assistant", "path"],
"filters": [["actor_class", "eq", "ai_referred_human"], ["is_entry", "eq", true]],
"order_by": [["sessions", "desc"]],
"limit": 20
}
confidence (confirmed, likely (default), all) sets which agent labels count as agents.
The query cookbook has a ready-made body for most questions.
Other endpoints
| Endpoint | Returns |
|---|---|
POST /v1/events |
Send custom events from your servers (Events and goals) |
GET /v1/sites, /v1/sites/{id} |
Your site and how it's installed |
GET /v1/operators |
Known agent operators and how each is verified |
GET /v1/sessions |
Agent and crawler sessions, newest first; page with cursor |
GET /v1/sessions/{id}, /v1/sessions/{id}/evidence |
One agent session, and why it was labelled an agent |
GET /v1/content/funnel, /v1/content/recommendations |
Agent content |
GET /v1/reconciliation |
GA4 comparison |
GET /v1/accounts |
Companies whose agents and people researched you, by intent, with a signal strength (Accounts) |
POST /v1/handoffs, GET /v1/handoffs |
Mint a handoff link for an agent (events:write), and see links opened and converted (Handoff links) |
GET /v1/ai-influence |
The post-conversion survey's estimate of AI-assisted conversions, against what links show (Events and goals) |
GET /v1/resolution |
Where agent sessions ran (in someone's browser, the cloud, a local tool) and how many joined a person or a company |
GET /v1/reports |
Saved reports shared with everyone on the site, with their Explore settings (Reports) |
Exports
For pulls bigger than a query returns, POST /v1/exports takes the same body as /v1/query with a limit of up to 100,000 rows (the default), and keeps the result as CSV for 24 hours. Rows withheld for privacy stay withheld (suppressed_rows says how many).
curl https://app.punchlineanalytics.com/v1/exports -H "Authorization: Bearer $PUNCHLINE_KEY" \
-d '{"date_range": {"from": "2026-09-01", "to": "2026-09-30"}, "metrics": ["pageviews"], "dimensions": ["day", "path", "operator"]}'
# → {"data": {"id": "…", "rows": 8412, "download_url": "https://…/v1/exports/…/download", …}}
curl --compressed -o september.csv -H "Authorization: Bearer $PUNCHLINE_KEY" "<download_url>"
Add "format": "parquet" for a Parquet file instead (typed columns, no gzip wrapper needed).
The CSV file is sent gzip-encoded, so use --compressed with curl (browsers and fetch decompress on their own). It's capped at 4 MB compressed; narrow the range or dimensions if you hit that. Cells that start with =, +, - or @ get a leading ', so a page path crafted as a formula can't run in a spreadsheet.
Managing your site
With a key that can manage (the admin scope):
| Endpoint | |
|---|---|
GET, POST /v1/alerts; GET, PATCH, DELETE /v1/alerts/{id} |
Alerts |
GET, POST /v1/goals; GET, PATCH, DELETE /v1/goals/{id} |
Goals |
GET, POST /v1/triggers; GET, PATCH, DELETE /v1/triggers/{id} |
No-code event triggers; changing one gives it a new version |
GET, PATCH, DELETE /v1/url-parameters |
The URL-parameter allowlist and rules |
PATCH takes only the fields you're changing. Writes accept Idempotency-Key, so a retry never creates a second alert. Validation is the same as in the dashboard, with the reason in error.message:
curl https://app.punchlineanalytics.com/v1/alerts \
-H "Authorization: Bearer $PUNCHLINE_KEY" -H "Idempotency-Key: agent-share-1" \
-d '{"name": "Agent share spike", "kind": "anomaly", "metric": "agent_share", "op": "up", "value": 50}'
Every parameter and response is in the API reference, also available as OpenAPI 3.1 at /v1/openapi.json.
TypeScript
@punchline-analytics/sdk is a typed client generated from the same OpenAPI spec, so its types always match the API. Install it with npm install @punchline-analytics/sdk; see TypeScript SDK for every method.
import { Punchline } from "@punchline-analytics/sdk";
const punchline = new Punchline({ apiKey: process.env.PUNCHLINE_API_KEY! });
const { results } = await punchline.query({
site_id: "<your site id>",
date_range: { from: "2026-09-01", to: "2026-09-30" },
metrics: ["sessions", "agent_share"],
dimensions: ["agent_purpose"],
});
// Every agent session, page by page:
for await (const session of punchline.allSessions({ from: "2026-09-24", to: "2026-09-30" })) {
console.log(session.type, session.operator, session.paths);
}
Errors throw PunchlineError with status, code and param; a 429 is retried after its Retry-After.
Errors
Every error has the same shape, with the parameter at fault when there is one:
{ "error": { "type": "invalid_request_error", "code": "invalid_parameter", "message": "limit: must be at most 1000", "param": "limit", "docs_url": "…/v1/errors#invalid_parameter" } }
Privacy
Outside your own dashboard, any group containing fewer than 5 people's page views is withheld and counted in meta.suppressed_rows. Groups of only agents and crawlers are always shown. Sessions of people are never returned; agent sessions are.
Warehouse export
To get data into BigQuery or Snowflake every day without calling the API, set up Warehouse export in Site settings. Each morning it writes yesterday's traffic, conversions, events and agent sessions into tables in your dataset or schema. It replaces each day's rows, so reruns are safe. For BigQuery, you share one dataset with Punchline's service account as BigQuery Data Editor and label it with your site ID; Snowflake uses a key pair Punchline generates, plus a setup script to run once. The Snowflake private key is stored encrypted. The same privacy rules apply as for the API.
Ask the docs
Answers come from these docs only, written by Claude (Anthropic). Your question is sent to answer it, and isn't stored.