API
API reference
Read your site's traffic split into people, AI agents and crawlers, with the evidence behind every label. Authenticate with an API key as a Bearer token. Each key is bound to one site. Outside your own dashboard, groups with fewer than 5 people's page views are withheld (meta.suppressed_rows), and sessions of people are never returned.
Base URL: https://app.punchlineanalytics.com. Authenticate with Authorization: Bearer pk_live_… (keys and scopes). Generated from the OpenAPI 3.1 spec that the API is tested against; every example below is a real response, recorded by calling the endpoint in our test suite against a sample site, so they can't drift from what the API returns.
- Query: POST /v1/query, POST /v1/exports, GET /v1/exports/{id}, GET /v1/exports/{id}/download. Metrics by dimensions, and exports for bigger pulls.
- Your site and agents: GET /v1/sites, GET /v1/sites/{id}, GET /v1/operators, GET /v1/sessions, GET /v1/sessions/{id}, GET /v1/sessions/{id}/evidence. The site behind the key, known operators, and agent sessions with their evidence.
- Reports: GET /v1/handoffs, GET /v1/content/funnel, GET /v1/content/recommendations, GET /v1/accounts, GET /v1/ai-influence, GET /v1/resolution, GET /v1/reconciliation, GET /v1/reports. The dashboard's reports as JSON.
- Send data: POST /v1/events, POST /v1/handoffs. Events from your servers and handoff links for agents.
- Manage: GET /v1/alerts, POST /v1/alerts, GET /v1/alerts/{id}, PATCH /v1/alerts/{id}, DELETE /v1/alerts/{id}, GET /v1/goals, POST /v1/goals, GET /v1/goals/{id}, PATCH /v1/goals/{id}, DELETE /v1/goals/{id}, GET /v1/triggers, POST /v1/triggers, GET /v1/triggers/{id}, PATCH /v1/triggers/{id}, DELETE /v1/triggers/{id}, GET /v1/url-parameters, PATCH /v1/url-parameters, DELETE /v1/url-parameters. Alerts, goals, event triggers and URL parameters. Needs the admin scope.
- Errors
Query
Metrics by dimensions, and exports for bigger pulls.
POST /v1/query
Metrics by dimensions, with filters. The main endpoint.
Scope: read (every key) · SDK: punchline.query()
Body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
site_id |
string (uuid) | yes | |
date_range |
object | yes | |
metrics |
list of sessions, pageviews, human_sessions, agent_sessions, agent_share, thin_pageviews, events, event_sessions, event_value, conversions, conversion_rate, visitors, bounces, bounce_rate, avg_session_duration, pages_per_session, engaged_sessions, engagement_rate, exits, exit_rate, avg_time_on_page |
yes | |
dimensions |
list of string | ||
filters |
list of any | ||
confidence |
confirmed, likely, all |
How strict to be about agent labels. confirmed: verified signatures and declared crawlers only. likely (default): adds high-confidence inferences. all: adds low-confidence patterns. Weaker labels count as human. | |
order_by |
list of any | ||
limit |
integer (1–1000) | Default 100 |
Example
curl -X POST "https://app.punchlineanalytics.com/v1/query" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"site_id": "$PUNCHLINE_SITE_ID",
"date_range": {
"from": "2026-10-06",
"to": "2026-10-06"
},
"metrics": [
"sessions",
"agent_share"
],
"dimensions": [
"visitor_type"
]
}'
Response 200:
{
"query": {
"site_id": "25cbb06c-e534-4f14-a740-785079ddc69c",
"date_range": {
"from": "2026-10-06",
"to": "2026-10-06"
},
"metrics": [
"sessions",
"agent_share"
],
"dimensions": [
"visitor_type"
],
"filters": [],
"confidence": "likely",
"order_by": [
[
"sessions",
"desc"
]
],
"limit": 100
},
"results": [
{
"visitor_type": "human",
"sessions": 9,
"agent_share": 0
},
{
"visitor_type": "ai_referred",
"sessions": 6,
"agent_share": 0
},
{
"visitor_type": "crawler",
"sessions": 1,
"agent_share": 1
},
{
"visitor_type": "agent",
"sessions": 1,
"agent_share": 1
}
],
"meta": {
"audience": "external",
"min_cohort": 5,
"suppressed_rows": 0,
"rows_before_limit": 4,
"truncated": false,
"rollups_refreshed_at": "2026-10-06T15:00:00.000Z"
},
"dashboard_url": "https://app.punchlineanalytics.com/dashboard?site=25cbb06c-e534-4f14-a740-785079ddc69c"
}
Responses
200: OK400:invalid_json,invalid_parameter401:invalid_api_key403:insufficient_scope404:site_not_found413:body_too_large429:rate_limited500:internal_error
POST /v1/exports
Run a query (the /v1/query model, limit up to 100,000, default 100,000) and keep the result as CSV for 24 hours. Same privacy suppression as /v1/query.
Scope: read (every key) · SDK: punchline.createExport()
Body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
site_id |
string (uuid) | yes | |
format |
csv |
||
date_range |
object | yes | |
metrics |
list of sessions, pageviews, human_sessions, agent_sessions, agent_share, thin_pageviews, events, event_sessions, event_value, conversions, conversion_rate, visitors, bounces, bounce_rate, avg_session_duration, pages_per_session, engaged_sessions, engagement_rate, exits, exit_rate, avg_time_on_page |
yes | |
dimensions |
list of string | ||
filters |
list of any | ||
confidence |
confirmed, likely, all |
How strict to be about agent labels. confirmed: verified signatures and declared crawlers only. likely (default): adds high-confidence inferences. all: adds low-confidence patterns. Weaker labels count as human. | |
order_by |
list of any | ||
limit |
integer (1–100000) |
Example
curl -X POST "https://app.punchlineanalytics.com/v1/exports" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"site_id": "$PUNCHLINE_SITE_ID",
"date_range": {
"from": "2026-10-06",
"to": "2026-10-06"
},
"metrics": [
"pageviews"
],
"dimensions": [
"path",
"visitor_type"
]
}'
Response 201:
{
"data": {
"id": "9ce4690d-fbee-412d-af39-cb4722e35406",
"status": "ready",
"format": "csv",
"query": {
"date_range": {
"from": "2026-10-06",
"to": "2026-10-06"
},
"metrics": [
"pageviews"
],
"dimensions": [
"path",
"visitor_type"
],
"filters": [],
"confidence": "likely",
"order_by": [
[
"pageviews",
"desc"
]
],
"limit": 100000
},
"rows": 11,
"suppressed_rows": 0,
"truncated": false,
"bytes": 142,
"created_at": "2026-10-06T15:00:00.000Z",
"expires_at": "2026-10-07T15:00:00.000Z",
"download_url": "https://app.punchlineanalytics.com/v1/exports/9ce4690d-fbee-412d-af39-cb4722e35406/download"
}
}
Responses
201: Created400:invalid_json,invalid_parameter401:invalid_api_key403:insufficient_scope404:site_not_found413:body_too_large429:rate_limited500:internal_error
GET /v1/exports/{id}
An export's size and download link.
Scope: read (every key) · SDK: punchline.getExport()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | string (uuid) | yes | The export's ID. |
Example
curl "https://app.punchlineanalytics.com/v1/exports/9ce4690d-fbee-412d-af39-cb4722e35406" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"data": {
"id": "9ce4690d-fbee-412d-af39-cb4722e35406",
"status": "ready",
"format": "csv",
"query": {
"date_range": {
"from": "2026-10-06",
"to": "2026-10-06"
},
"metrics": [
"pageviews"
],
"dimensions": [
"path",
"visitor_type"
],
"filters": [],
"confidence": "likely",
"order_by": [
[
"pageviews",
"desc"
]
],
"limit": 100000
},
"rows": 11,
"suppressed_rows": 0,
"truncated": false,
"bytes": 142,
"created_at": "2026-10-06T15:00:00.000Z",
"expires_at": "2026-10-07T15:00:00.000Z",
"download_url": "https://app.punchlineanalytics.com/v1/exports/9ce4690d-fbee-412d-af39-cb4722e35406/download"
}
}
Responses
200: OK401:invalid_api_key403:insufficient_scope404:not_found429:rate_limited500:internal_error
GET /v1/exports/{id}/download
The CSV, gzip-encoded (Content-Encoding: gzip; with curl, use --compressed). Cells starting with = + - @ are prefixed with ' so spreadsheets don't run them.
Scope: read (every key) · SDK: punchline.downloadExport()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | string (uuid) | yes | The export's ID. |
Responses
200: CSV401:invalid_api_key403:insufficient_scope404:not_found429:rate_limited500:internal_error
Your site and agents
The site behind the key, known operators, and agent sessions with their evidence.
GET /v1/sites
The site this key can read, with setup status.
Scope: read (every key) · SDK: punchline.listSites()
Example
curl "https://app.punchlineanalytics.com/v1/sites" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"data": [
{
"id": "25cbb06c-e534-4f14-a740-785079ddc69c",
"name": "Shop",
"domain": "shop.example",
"setup": {
"edge_install_reporting": true,
"snippet_reporting": false,
"browser_only": false,
"ga4_connected": true
},
"url_params": []
}
]
}
Responses
200: OK401:invalid_api_key403:insufficient_scope429:rate_limited500:internal_error
GET /v1/sites/{id}
One site.
Scope: read (every key) · SDK: punchline.getSite()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | string (uuid) | yes |
Example
curl "https://app.punchlineanalytics.com/v1/sites/$PUNCHLINE_SITE_ID" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"data": {
"id": "25cbb06c-e534-4f14-a740-785079ddc69c",
"name": "Shop",
"domain": "shop.example",
"setup": {
"edge_install_reporting": true,
"snippet_reporting": false,
"browser_only": false,
"ga4_connected": true
},
"url_params": []
}
}
Responses
200: OK401:invalid_api_key403:insufficient_scope404:site_not_found429:rate_limited500:internal_error
GET /v1/operators
Known agent operators and how each is verified.
Scope: read (every key) · SDK: punchline.listOperators()
Example
curl "https://app.punchlineanalytics.com/v1/operators" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"registry_version": "2026-10-25.1",
"data": [
{
"name": "GPTBot",
"operator": "openai",
"type": "crawler",
"actor_class": "crawler",
"verified_by": [
"published_ip_ranges"
],
"docs_url": "https://platform.openai.com/docs/bots"
},
{
"name": "OAI-SearchBot",
"operator": "openai",
"type": "crawler",
"actor_class": "crawler",
"verified_by": [
"published_ip_ranges"
],
"docs_url": "https://platform.openai.com/docs/bots"
}
]
}
Responses
200: OK401:invalid_api_key403:insufficient_scope429:rate_limited500:internal_error
GET /v1/sessions
Agent and crawler sessions, newest first. Page with cursor=next_cursor.
Scope: read (every key) · SDK: punchline.listSessions()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
site_id |
query | string (uuid) | Optional; must be the API key's site. | |
from |
query | string (date) | First UTC day, YYYY-MM-DD. Default: 6 days before to. |
|
to |
query | string (date) | Last UTC day, YYYY-MM-DD, inclusive. Default: today. | |
actor_class |
query | signed_agent, declared_agent, crawler, fetcher, suspected_agent, spoofed_bot |
||
operator |
query | string | ||
tier |
query | verified, declared, inferred_high, inferred_low |
||
limit |
query | integer (1–100) | Default 25 | |
cursor |
query | string |
Example
curl "https://app.punchlineanalytics.com/v1/sessions?site_id=$PUNCHLINE_SITE_ID&from=2026-10-06&to=2026-10-06&limit=2" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"data": [
{
"session_id": "d9828ab0-ecf1-4046-a47f-dabd6a37e60e",
"type": "declared agent",
"actor_class": "declared_agent",
"operator": "openai",
"operator_docs": "https://platform.openai.com/docs/bots",
"tier": "inferred_high",
"started_at": "2026-10-06T13:30:00.000Z",
"ended_at": "2026-10-06T13:32:00.000Z",
"pageviews": 3,
"paths": [
"/pricing",
"/security",
"/compare/acme-vs-shop"
],
"rules": [
{
"id": "R2u",
"description": "Declares a known bot that couldn't be confirmed: no IP data, DNS failed, or an address outside the operator's published list on a network it's known to use"
}
],
"evidence": {
"ua_claims": "ChatGPT-User",
"claim_verification": "unavailable",
"claim": "unverifiable",
"asn": 16509
}
},
{
"session_id": "4d7e7cb2-df1a-40c3-a4a2-54c208b008f2",
"type": "crawler",
"actor_class": "crawler",
"operator": "openai",
"operator_docs": "https://platform.openai.com/docs/bots",
"tier": "declared",
"started_at": "2026-10-06T13:00:00.000Z",
"ended_at": "2026-10-06T13:03:00.000Z",
"pageviews": 4,
"paths": [
"/",
"/pricing",
"/docs/api",
"/security"
],
"rules": [
{
"id": "R2",
"description": "Declares a known bot, and its IP matches the operator's published ranges or reverse DNS"
}
],
"evidence": {
"ua_claims": "GPTBot",
"ip_in_published_range": true,
"claim": "confirmed"
}
}
],
"next_cursor": null
}
Responses
200: OK400:invalid_parameter401:invalid_api_key403:insufficient_scope404:site_not_found429:rate_limited500:internal_error
GET /v1/sessions/{id}
One agent session as a request timeline.
Scope: read (every key) · SDK: punchline.getSession()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | string (uuid) | yes |
Example
curl "https://app.punchlineanalytics.com/v1/sessions/d9828ab0-ecf1-4046-a47f-dabd6a37e60e?site_id=$PUNCHLINE_SITE_ID" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"data": {
"session_id": "d9828ab0-ecf1-4046-a47f-dabd6a37e60e",
"type": "declared agent",
"actor_class": "declared_agent",
"operator": "openai",
"operator_docs": "https://platform.openai.com/docs/bots",
"tier": "inferred_high",
"started_at": "2026-10-06T13:30:00.000Z",
"ended_at": "2026-10-06T13:32:00.000Z",
"pageviews": 3,
"paths": [
"/pricing",
"/security",
"/compare/acme-vs-shop"
],
"rules": [
{
"id": "R2u",
"description": "Declares a known bot that couldn't be confirmed: no IP data, DNS failed, or an address outside the operator's published list on a network it's known to use"
}
],
"evidence": {
"ua_claims": "ChatGPT-User",
"claim_verification": "unavailable",
"claim": "unverifiable",
"asn": 16509
}
},
"dashboard_url": "https://app.punchlineanalytics.com/dashboard/agents/sessions?site=25cbb06c-e534-4f14-a740-785079ddc69c&operator=openai&class=declared_agent&tier=inferred_high"
}
Responses
200: OK400:invalid_parameter401:invalid_api_key403:insufficient_scope404:not_found429:rate_limited500:internal_error
GET /v1/sessions/{id}/evidence
Why this session was labelled an agent: rules, evidence, tier.
Scope: read (every key) · SDK: punchline.getSessionEvidence()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | string (uuid) | yes |
Example
curl "https://app.punchlineanalytics.com/v1/sessions/d9828ab0-ecf1-4046-a47f-dabd6a37e60e/evidence?site_id=$PUNCHLINE_SITE_ID" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"data": {
"session_id": "d9828ab0-ecf1-4046-a47f-dabd6a37e60e",
"type": "declared agent",
"actor_class": "declared_agent",
"operator": "openai",
"tier": "inferred_high",
"rules": [
{
"id": "R2u",
"description": "Declares a known bot that couldn't be confirmed: no IP data, DNS failed, or an address outside the operator's published list on a network it's known to use"
}
],
"evidence": {
"ua_claims": "ChatGPT-User",
"claim_verification": "unavailable",
"claim": "unverifiable",
"asn": 16509
}
}
}
Responses
200: OK400:invalid_parameter401:invalid_api_key403:insufficient_scope404:not_found429:rate_limited500:internal_error
Reports
The dashboard's reports as JSON.
GET /v1/handoffs
Handoff links created in a date range: opened by a person, opened by an agent, and converted (the opener's session completed a goal), with values, per kind and agent operator.
Scope: read (every key) · SDK: punchline.getHandoffs()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
site_id |
query | string (uuid) | Optional; must be the API key's site. | |
from |
query | string (date) | First UTC day, YYYY-MM-DD. Default: 6 days before to. |
|
to |
query | string (date) | Last UTC day, YYYY-MM-DD, inclusive. Default: today. |
Example
curl "https://app.punchlineanalytics.com/v1/handoffs?site_id=$PUNCHLINE_SITE_ID&from=2026-10-06&to=2026-10-06" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"data": {
"date_range": {
"from": "2026-10-06",
"to": "2026-10-06"
},
"totals": {
"created": 1,
"opened_by_person": 0,
"opened_by_agent": 0,
"converted": 0,
"value_created": 249,
"value_opened": 0,
"value_converted": 0
},
"by_kind": [
{
"kind": "cart",
"created": 1,
"opened_by_person": 0,
"opened_by_agent": 0,
"converted": 0,
"value_created": 249,
"value_opened": 0,
"value_converted": 0
}
],
"by_operator": [
{
"operator": "openai",
"created": 1,
"opened_by_person": 0,
"opened_by_agent": 0,
"converted": 0,
"value_created": 249,
"value_opened": 0,
"value_converted": 0
}
]
},
"dashboard_url": "https://app.punchlineanalytics.com/dashboard/handoffs?site=25cbb06c-e534-4f14-a740-785079ddc69c"
}
Responses
200: OK400:invalid_parameter401:invalid_api_key403:insufficient_scope404:site_not_found409:feature_unavailable429:rate_limited500:internal_error
GET /v1/content/funnel
Agent content funnel: fetched (live vs crawl) → delivered → cited, per page, content type or topic.
Scope: read (every key) · SDK: punchline.getContentFunnel()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
site_id |
query | string (uuid) | Optional; must be the API key's site. | |
from |
query | string (date) | First UTC day, YYYY-MM-DD. Default: 6 days before to. |
|
to |
query | string (date) | Last UTC day, YYYY-MM-DD, inclusive. Default: today. | |
confidence |
query | confirmed, likely, all |
How strict to be about agent labels. confirmed: verified signatures and declared crawlers only. likely (default): adds high-confidence inferences. all: adds low-confidence patterns. Weaker labels count as human. | |
group_by |
query | path, content_type, topic |
Group pages by path (default), content type or topic. | |
limit |
query | integer (1–200) | Default 25 |
Example
curl "https://app.punchlineanalytics.com/v1/content/funnel?site_id=$PUNCHLINE_SITE_ID&from=2026-10-06&to=2026-10-06&limit=3" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"data": {
"date_range": {
"from": "2026-10-06",
"to": "2026-10-06"
},
"group_by": "path",
"rows": [
{
"key": "/pricing",
"fetches": {
"total": 2,
"live": 1,
"crawl": 1,
"other": 0
},
"delivery": {
"known": 2,
"ok": 2,
"redirect": 0,
"failed": 0,
"not_found": 0,
"blocked": 0,
"thin": 0
},
"delivered_share": 1,
"cited": 6,
"citation_rate": 600,
"converted": 6,
"impostor_fetches": 0
},
{
"key": "/security",
"fetches": {
"total": 2,
"live": 1,
"crawl": 1,
"other": 0
},
"delivery": {
"known": 2,
"ok": 2,
"redirect": 0,
"failed": 0,
"not_found": 0,
"blocked": 0,
"thin": 0
},
"delivered_share": 1,
"cited": 0,
"citation_rate": 0,
"converted": 0,
"impostor_fetches": 0
},
{
"key": "/",
"fetches": {
"total": 1,
"live": 0,
"crawl": 1,
"other": 0
},
"delivery": {
"known": 1,
"ok": 1,
"redirect": 0,
"failed": 0,
"not_found": 0,
"blocked": 0,
"thin": 0
},
"delivered_share": 1,
"cited": 0,
"citation_rate": null,
"converted": 0,
"impostor_fetches": 0
}
],
"totals": {
"key": "all",
"fetches": {
"total": 7,
"live": 3,
"crawl": 4,
"other": 0
},
"delivery": {
"known": 7,
"ok": 7,
"redirect": 0,
"failed": 0,
"not_found": 0,
"blocked": 0,
"thin": 0
},
"delivered_share": 1,
"cited": 6,
"citation_rate": 200,
"converted": 6,
"impostor_fetches": 0
},
"delivery_coverage": 1,
"confidence": "likely"
},
"dashboard_url": "https://app.punchlineanalytics.com/dashboard/content?site=25cbb06c-e534-4f14-a740-785079ddc69c&confidence=likely"
}
Responses
200: OK400:invalid_parameter401:invalid_api_key403:insufficient_scope404:site_not_found429:rate_limited500:internal_error
GET /v1/content/recommendations
Evidence-based things to try so assistants can read and cite the site, with the previous period for comparison.
Scope: read (every key) · SDK: punchline.getRecommendations()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
site_id |
query | string (uuid) | Optional; must be the API key's site. | |
from |
query | string (date) | First UTC day, YYYY-MM-DD. Default: 6 days before to. |
|
to |
query | string (date) | Last UTC day, YYYY-MM-DD, inclusive. Default: today. | |
confidence |
query | confirmed, likely, all |
How strict to be about agent labels. confirmed: verified signatures and declared crawlers only. likely (default): adds high-confidence inferences. all: adds low-confidence patterns. Weaker labels count as human. |
Example
curl "https://app.punchlineanalytics.com/v1/content/recommendations?site_id=$PUNCHLINE_SITE_ID&from=2026-10-06&to=2026-10-06" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"data": {
"date_range": {
"from": "2026-10-06",
"to": "2026-10-06"
},
"previous_range": {
"from": "2026-10-05",
"to": "2026-10-05"
},
"delivery_coverage": 1,
"findings": [],
"confidence": "likely"
},
"dashboard_url": "https://app.punchlineanalytics.com/dashboard/content?site=25cbb06c-e534-4f14-a740-785079ddc69c&confidence=likely"
}
Responses
200: OK400:invalid_parameter401:invalid_api_key403:insufficient_scope404:site_not_found429:rate_limited500:internal_error
GET /v1/accounts
Agent intent by company: what each company's agents and people read, grouped by intent, with a strength and a summary. Account-level only.
Scope: read (every key) · SDK: punchline.getAccounts()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
site_id |
query | string (uuid) | Optional; must be the API key's site. | |
from |
query | string (date) | First UTC day, YYYY-MM-DD. Default: 6 days before to. |
|
to |
query | string (date) | Last UTC day, YYYY-MM-DD, inclusive. Default: today. | |
confidence |
query | confirmed, likely, all |
How strict to be about agent labels. confirmed: verified signatures and declared crawlers only. likely (default): adds high-confidence inferences. all: adds low-confidence patterns. Weaker labels count as human. | |
company_domain |
query | string | One company, e.g. acme.com | |
min_strength |
query | light, moderate, strong |
Default light (all accounts) | |
limit |
query | integer (1–200) | Default 25 |
Example
curl "https://app.punchlineanalytics.com/v1/accounts?site_id=$PUNCHLINE_SITE_ID&from=2026-10-06&to=2026-10-06" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"data": {
"date_range": {
"from": "2026-10-06",
"to": "2026-10-06"
},
"confidence": "likely",
"accounts": [
{
"company_domain": "acme.com",
"method": "form_domain",
"strength": "moderate",
"score": 9,
"summary": "Agents read pricing, security and comparison pages 3 times",
"agents": {
"sessions": 1,
"pageviews": 3,
"operators": [
"openai"
],
"runtimes": [
"cloud"
]
},
"people": null,
"intents": [
{
"intent": "pricing",
"label": "Pricing",
"agent_pageviews": 1,
"people_pageviews": null
},
{
"intent": "security",
"label": "Security & compliance",
"agent_pageviews": 1,
"people_pageviews": null
},
{
"intent": "comparison",
"label": "Comparisons",
"agent_pageviews": 1,
"people_pageviews": null
}
],
"previous_agent_sessions": 0,
"first_seen": "2026-10-06",
"last_seen": "2026-10-06"
}
],
"meta": {
"withheld_people": 0
}
},
"dashboard_url": "https://app.punchlineanalytics.com/dashboard/accounts?site=25cbb06c-e534-4f14-a740-785079ddc69c&confidence=likely"
}
Responses
200: OK400:invalid_parameter401:invalid_api_key403:insufficient_scope404:site_not_found409:feature_unavailable429:rate_limited500:internal_error
GET /v1/ai-influence
Estimated AI-assisted conversions from the post-conversion survey (ai_assist_yes / _no / _not_sure events), with a 95% interval, against conversions Punchline connects directly, and the multiplier.
Scope: read (every key) · SDK: punchline.getAiInfluence()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
site_id |
query | string (uuid) | Optional; must be the API key's site. | |
from |
query | string (date) | First UTC day, YYYY-MM-DD. Default: 6 days before to. |
|
to |
query | string (date) | Last UTC day, YYYY-MM-DD, inclusive. Default: today. | |
confidence |
query | confirmed, likely, all |
How strict to be about agent labels. confirmed: verified signatures and declared crawlers only. likely (default): adds high-confidence inferences. all: adds low-confidence patterns. Weaker labels count as human. | |
goal |
query | string | One goal's conversions; default all goals |
Example
curl "https://app.punchlineanalytics.com/v1/ai-influence?site_id=$PUNCHLINE_SITE_ID&from=2026-10-06&to=2026-10-06" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"data": {
"date_range": {
"from": "2026-10-06",
"to": "2026-10-06"
},
"confidence": "likely",
"responses": {
"yes": 14,
"no": 22,
"not_sure": 6
},
"yes_share": 0.3888888888888889,
"yes_share_interval": [
0.24784655417822943,
0.5513582970325652
],
"enough_responses": true,
"people_conversions": 6,
"estimated_ai_assisted": {
"low": 1.4870793250693768,
"mid": 2.3333333333333335,
"high": 3.3081497821953914
},
"observed": {
"ai_referred": 6,
"handoffs": 0,
"total": 6
},
"multiplier": 0.3888888888888889
},
"dashboard_url": "https://app.punchlineanalytics.com/dashboard/goals?site=25cbb06c-e534-4f14-a740-785079ddc69c&confidence=likely"
}
Responses
200: OK400:invalid_parameter401:invalid_api_key403:insufficient_scope404:site_not_found429:rate_limited500:internal_error
GET /v1/resolution
Where agent sessions ran (in a person's browser, cloud, local tool) and how many joined a person's visit or a company the same day.
Scope: read (every key) · SDK: punchline.getResolution()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
site_id |
query | string (uuid) | Optional; must be the API key's site. | |
from |
query | string (date) | First UTC day, YYYY-MM-DD. Default: 6 days before to. |
|
to |
query | string (date) | Last UTC day, YYYY-MM-DD, inclusive. Default: today. | |
confidence |
query | confirmed, likely, all |
How strict to be about agent labels. confirmed: verified signatures and declared crawlers only. likely (default): adds high-confidence inferences. all: adds low-confidence patterns. Weaker labels count as human. |
Example
curl "https://app.punchlineanalytics.com/v1/resolution?site_id=$PUNCHLINE_SITE_ID&from=2026-10-06&to=2026-10-06" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"data": {
"date_range": {
"from": "2026-10-06",
"to": "2026-10-06"
},
"confidence": "likely",
"agents": {
"sessions": 1,
"by_runtime": {
"in_browser": 0,
"cloud": 1,
"local": 0,
"unknown": 0
},
"in_browser_share": 0,
"joined_person": 0,
"joined_company": 1,
"methods": {
"same_day_visit": 0,
"company_domain": 1,
"company_ip": 0,
"handoff": 0,
"any": 1
},
"connected_by_runtime": {
"in_browser": 0,
"cloud": 1,
"local": 0,
"unknown": 0
}
},
"crawlers": {
"sessions": 1
},
"days": [
{
"day": "2026-10-06",
"in_browser": 0,
"cloud": 1,
"local": 0,
"unknown": 0
}
],
"operators": [
{
"operator": "openai",
"sessions": 1,
"by_runtime": {
"in_browser": 0,
"cloud": 1,
"local": 0,
"unknown": 0
},
"joined_person": 0
}
]
},
"dashboard_url": "https://app.punchlineanalytics.com/dashboard/resolution?site=25cbb06c-e534-4f14-a740-785079ddc69c&confidence=likely"
}
Responses
200: OK400:invalid_parameter401:invalid_api_key403:insufficient_scope404:site_not_found409:feature_unavailable429:rate_limited500:internal_error
GET /v1/reconciliation
GA4 sessions vs Punchline's, with the gap explained. Needs GA4 connected.
Scope: read (every key) · SDK: punchline.getReconciliation()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
site_id |
query | string (uuid) | Optional; must be the API key's site. | |
from |
query | string (date) | First UTC day, YYYY-MM-DD. Default: 6 days before to. |
|
to |
query | string (date) | Last UTC day, YYYY-MM-DD, inclusive. Default: today. | |
confidence |
query | confirmed, likely, all |
How strict to be about agent labels. confirmed: verified signatures and declared crawlers only. likely (default): adds high-confidence inferences. all: adds low-confidence patterns. Weaker labels count as human. |
Example
curl "https://app.punchlineanalytics.com/v1/reconciliation?site_id=$PUNCHLINE_SITE_ID&from=2026-10-06&to=2026-10-06" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"data": {
"date_range": {
"from": "2026-10-06",
"to": "2026-10-06"
},
"confidence": "likely",
"totals": {
"ga4": 14,
"ourHumans": 15,
"agentsGa4NeverSees": 2,
"agentsGa4CountsAsPeople": 0,
"unexplained": -1,
"daysWithGa4": 1
},
"days": [
{
"day": "2026-10-06",
"ga4": 14,
"ourHumans": 15,
"agentsGa4NeverSees": 2,
"agentsGa4CountsAsPeople": 0,
"unexplained": -1
}
],
"estimate_note": "agentsGa4CountsAsPeople is an estimate: browser-based agents run GA4's script, so GA4 likely counted them."
},
"dashboard_url": "https://app.punchlineanalytics.com/dashboard/reconciliation?site=25cbb06c-e534-4f14-a740-785079ddc69c&confidence=likely"
}
Responses
200: OK400:invalid_parameter401:invalid_api_key403:insufficient_scope404:site_not_found409:feature_unavailable429:rate_limited500:internal_error
GET /v1/reports
Saved reports shared with this site (private reports are never listed), with their Explore parameters: tm metric, d breakdown, f filters, tt chart type, tg granularity, range, who, confidence.
Scope: read (every key) · SDK: punchline.listReports()
Example
curl "https://app.punchlineanalytics.com/v1/reports" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"data": [
{
"id": "1a85afd3-851d-48e9-a9ae-aca544ff6ea9",
"name": "AI referrals by assistant",
"params": {
"tm": "sessions",
"bd": "assistant"
},
"updated_at": "2026-10-06T15:00:00.000Z"
}
]
}
Responses
200: OK401:invalid_api_key403:insufficient_scope429:rate_limited500:internal_error
Send data
Events from your servers and handoff links for agents.
POST /v1/events
Send custom events from your servers (up to 1,000). Needs the events:write scope. Send Idempotency-Key to make retries safe.
Scope: events:write (keys that can send events) · SDK: punchline.sendEvents()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | string | Retries with the same key and body return the first response. |
Body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
site_id |
string (uuid) | yes | |
events |
list of EventInput | yes |
Example
curl -X POST "https://app.punchlineanalytics.com/v1/events" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"site_id": "$PUNCHLINE_SITE_ID",
"events": [
{
"name": "signup",
"value": 49,
"currency": "USD",
"props": {
"plan": "team"
}
}
]
}'
Response 202:
{
"accepted": 1
}
Responses
202: Accepted400:invalid_json,invalid_parameter401:invalid_api_key403:insufficient_scope404:site_not_found413:body_too_large422:idempotency_key_reused429:rate_limited500:internal_error
POST /v1/handoffs
Mint a handoff link for an agent that built a cart, quote, configuration or booking. Give the agent's request (visitor) to tie the link to its session; when a person opens the link, the open is recorded. Needs the events:write scope. Only the token's hash is stored.
Scope: events:write (keys that can send events) · SDK: punchline.createHandoff()
Body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
site_id |
string (uuid) | yes | |
kind |
cart, quote, configuration, booking, other |
||
value |
number (0–) | The cart or quote total | |
currency |
string | ||
expires_in_days |
integer (1–90) | ||
url |
string | A path or https URL on your site; the response adds the token to it | |
visitor |
object | The agent's request, used in memory only |
Example
curl -X POST "https://app.punchlineanalytics.com/v1/handoffs" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"site_id": "$PUNCHLINE_SITE_ID",
"kind": "cart",
"value": 249,
"currency": "USD",
"url": "/cart/8431",
"visitor": {
"ip": "3.0.0.9",
"user_agent": "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ChatGPT-User/1.0; +https://openai.com/bot"
}
}'
Response 201:
{
"data": {
"token": "TLDqSZynF30ytN624lHQqPhX",
"param": "pl_ho",
"url": "https://shop.example/cart/8431?pl_ho=TLDqSZynF30ytN624lHQqPhX",
"kind": "cart",
"expires_at": "2026-11-05T15:00:00.000Z",
"agent": {
"session_found": false,
"actor_class": "declared_agent",
"operator": "openai"
}
}
}
Responses
201: Created400:invalid_json,invalid_parameter401:invalid_api_key403:insufficient_scope404:site_not_found429:rate_limited500:internal_error
Manage
Alerts, goals, event triggers and URL parameters. Needs the admin scope.
GET /v1/alerts
The site's alerts. Needs the admin scope.
Scope: admin (keys that can manage) · SDK: punchline.listAlerts()
Example
curl "https://app.punchlineanalytics.com/v1/alerts" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"data": [
{
"id": "5620320e-d3c1-4405-a004-6c9b1cf8e994",
"name": "Agent share spike",
"kind": "anomaly",
"metric": "agent_share",
"filters": [],
"confidence": "likely",
"op": "up",
"value": 50,
"min_volume": 20,
"active": true,
"description": "Agent share rises by 50% or more on the usual, when the usual volume is at least 20.",
"created_at": "2026-10-06T15:00:00.000Z"
}
]
}
Responses
200: OK401:invalid_api_key403:insufficient_scope429:rate_limited500:internal_error
POST /v1/alerts
Create a alert. Needs the admin scope.
Scope: admin (keys that can manage) · SDK: punchline.createAlert()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | string | Retries with the same key and body return the first response. |
Body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ||
kind |
threshold, anomaly, new_operator, account_signal |
||
metric |
string | A /v1/query metric | |
filters |
list of any | ||
confidence |
confirmed, likely, all |
||
op |
above, below, up, down, either |
||
value |
number | ||
min_volume |
integer (0–) | ||
active |
boolean |
Example
curl -X POST "https://app.punchlineanalytics.com/v1/alerts" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Agent share spike",
"kind": "anomaly",
"metric": "agent_share",
"op": "up",
"value": 50
}'
Response 201:
{
"data": {
"id": "5620320e-d3c1-4405-a004-6c9b1cf8e994",
"name": "Agent share spike",
"kind": "anomaly",
"metric": "agent_share",
"filters": [],
"confidence": "likely",
"op": "up",
"value": 50,
"min_volume": 20,
"active": true,
"description": "Agent share rises by 50% or more on the usual, when the usual volume is at least 20.",
"created_at": "2026-10-06T15:00:00.000Z"
}
}
Responses
201: Created400:invalid_json,invalid_parameter401:invalid_api_key403:insufficient_scope404:site_not_found413:body_too_large422:idempotency_key_reused429:rate_limited500:internal_error
GET /v1/alerts/{id}
One alert.
Scope: admin (keys that can manage) · SDK: punchline.getAlert()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | string (uuid) | yes | The alert's ID. |
Example
curl "https://app.punchlineanalytics.com/v1/alerts/5620320e-d3c1-4405-a004-6c9b1cf8e994" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"data": {
"id": "5620320e-d3c1-4405-a004-6c9b1cf8e994",
"name": "Agent share spike",
"kind": "anomaly",
"metric": "agent_share",
"filters": [],
"confidence": "likely",
"op": "up",
"value": 50,
"min_volume": 20,
"active": true,
"description": "Agent share rises by 50% or more on the usual, when the usual volume is at least 20.",
"created_at": "2026-10-06T15:00:00.000Z"
}
}
Responses
200: OK401:invalid_api_key403:insufficient_scope404:not_found429:rate_limited500:internal_error
PATCH /v1/alerts/{id}
Change a alert: send only the fields to change.
Scope: admin (keys that can manage) · SDK: punchline.updateAlert()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | string (uuid) | yes | The alert's ID. |
Idempotency-Key |
header | string | Retries with the same key and body return the first response. |
Body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ||
kind |
threshold, anomaly, new_operator, account_signal |
||
metric |
string | A /v1/query metric | |
filters |
list of any | ||
confidence |
confirmed, likely, all |
||
op |
above, below, up, down, either |
||
value |
number | ||
min_volume |
integer (0–) | ||
active |
boolean |
Example
curl -X PATCH "https://app.punchlineanalytics.com/v1/alerts/5620320e-d3c1-4405-a004-6c9b1cf8e994" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"value": 80
}'
Response 200:
{
"data": {
"id": "5620320e-d3c1-4405-a004-6c9b1cf8e994",
"name": "Agent share spike",
"kind": "anomaly",
"metric": "agent_share",
"filters": [],
"confidence": "likely",
"op": "up",
"value": 80,
"min_volume": 20,
"active": true,
"description": "Agent share rises by 80% or more on the usual, when the usual volume is at least 20.",
"created_at": "2026-10-06T15:00:00.000Z"
}
}
Responses
200: OK400:invalid_json,invalid_parameter401:invalid_api_key403:insufficient_scope404:site_not_found,not_found413:body_too_large422:idempotency_key_reused429:rate_limited500:internal_error
DELETE /v1/alerts/{id}
Delete a alert.
Scope: admin (keys that can manage) · SDK: punchline.deleteAlert()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | string (uuid) | yes | The alert's ID. |
Idempotency-Key |
header | string | Retries with the same key and body return the first response. |
Example
curl -X DELETE "https://app.punchlineanalytics.com/v1/alerts/5620320e-d3c1-4405-a004-6c9b1cf8e994" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 204, no body.
Responses
204: Deleted401:invalid_api_key403:insufficient_scope404:not_found422:idempotency_key_reused429:rate_limited500:internal_error
GET /v1/goals
The site's goals. Needs the admin scope.
Scope: admin (keys that can manage) · SDK: punchline.listGoals()
Example
curl "https://app.punchlineanalytics.com/v1/goals" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"data": [
{
"id": "242cd0db-2502-4eb9-a311-f9fb4bdc7e41",
"name": "Purchase",
"kind": "page",
"match": "=/thanks"
},
{
"id": "398a21ce-1622-4d5d-a136-527782fc885c",
"name": "Signed up",
"kind": "event",
"match": "signup"
}
]
}
Responses
200: OK401:invalid_api_key403:insufficient_scope429:rate_limited500:internal_error
POST /v1/goals
Create a goal. Needs the admin scope.
Scope: admin (keys that can manage) · SDK: punchline.createGoal()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | string | Retries with the same key and body return the first response. |
Body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ||
kind |
event, page |
||
match |
string | An event name, or a path (=/path for exactly that page) |
Example
curl -X POST "https://app.punchlineanalytics.com/v1/goals" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Signed up",
"kind": "event",
"match": "signup"
}'
Response 201:
{
"data": {
"id": "398a21ce-1622-4d5d-a136-527782fc885c",
"name": "Signed up",
"kind": "event",
"match": "signup"
}
}
Responses
201: Created400:invalid_json,invalid_parameter401:invalid_api_key403:insufficient_scope404:site_not_found413:body_too_large422:idempotency_key_reused429:rate_limited500:internal_error
GET /v1/goals/{id}
One goal.
Scope: admin (keys that can manage) · SDK: punchline.getGoal()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | string (uuid) | yes | The goal's ID. |
Example
curl "https://app.punchlineanalytics.com/v1/goals/398a21ce-1622-4d5d-a136-527782fc885c" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"data": {
"id": "398a21ce-1622-4d5d-a136-527782fc885c",
"name": "Signed up",
"kind": "event",
"match": "signup"
}
}
Responses
200: OK401:invalid_api_key403:insufficient_scope404:not_found429:rate_limited500:internal_error
PATCH /v1/goals/{id}
Change a goal: send only the fields to change.
Scope: admin (keys that can manage) · SDK: punchline.updateGoal()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | string (uuid) | yes | The goal's ID. |
Idempotency-Key |
header | string | Retries with the same key and body return the first response. |
Body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ||
kind |
event, page |
||
match |
string | An event name, or a path (=/path for exactly that page) |
Example
curl -X PATCH "https://app.punchlineanalytics.com/v1/goals/398a21ce-1622-4d5d-a136-527782fc885c" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Signup"
}'
Response 200:
{
"data": {
"id": "398a21ce-1622-4d5d-a136-527782fc885c",
"name": "Signup",
"kind": "event",
"match": "signup"
}
}
Responses
200: OK400:invalid_json,invalid_parameter401:invalid_api_key403:insufficient_scope404:site_not_found,not_found413:body_too_large422:idempotency_key_reused429:rate_limited500:internal_error
DELETE /v1/goals/{id}
Delete a goal.
Scope: admin (keys that can manage) · SDK: punchline.deleteGoal()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | string (uuid) | yes | The goal's ID. |
Idempotency-Key |
header | string | Retries with the same key and body return the first response. |
Example
curl -X DELETE "https://app.punchlineanalytics.com/v1/goals/398a21ce-1622-4d5d-a136-527782fc885c" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 204, no body.
Responses
204: Deleted401:invalid_api_key403:insufficient_scope404:not_found422:idempotency_key_reused429:rate_limited500:internal_error
GET /v1/triggers
The site's no-code event triggers. Needs the admin scope.
Scope: admin (keys that can manage) · SDK: punchline.listTriggers()
Example
curl "https://app.punchlineanalytics.com/v1/triggers" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"data": [
{
"id": "92f7364b-89e3-43f0-aa7c-91954aa9b0af",
"name": "Demo button",
"kind": "click",
"match": "#book-demo",
"event_name": "demo_click",
"version": 1,
"active": true
}
]
}
Responses
200: OK401:invalid_api_key403:insufficient_scope429:rate_limited500:internal_error
POST /v1/triggers
Create a trigger. Needs the admin scope.
Scope: admin (keys that can manage) · SDK: punchline.createTrigger()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | string | Retries with the same key and body return the first response. |
Body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ||
kind |
page_view, url_match, click, form_submit |
||
match |
string | A path (page_view, url_match with * wildcards) or a CSS selector (click, form_submit; needs the browser snippet) | |
event_name |
string | ||
active |
boolean |
Example
curl -X POST "https://app.punchlineanalytics.com/v1/triggers" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Demo button",
"kind": "click",
"match": "#book-demo",
"event_name": "demo_click"
}'
Response 201:
{
"data": {
"id": "92f7364b-89e3-43f0-aa7c-91954aa9b0af",
"name": "Demo button",
"kind": "click",
"match": "#book-demo",
"event_name": "demo_click",
"version": 1,
"active": true
}
}
Responses
201: Created400:invalid_json,invalid_parameter401:invalid_api_key403:insufficient_scope404:site_not_found413:body_too_large422:idempotency_key_reused429:rate_limited500:internal_error
GET /v1/triggers/{id}
One trigger.
Scope: admin (keys that can manage) · SDK: punchline.getTrigger()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | string (uuid) | yes | The trigger's ID. |
Example
curl "https://app.punchlineanalytics.com/v1/triggers/92f7364b-89e3-43f0-aa7c-91954aa9b0af" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"data": {
"id": "92f7364b-89e3-43f0-aa7c-91954aa9b0af",
"name": "Demo button",
"kind": "click",
"match": "#book-demo",
"event_name": "demo_click",
"version": 1,
"active": true
}
}
Responses
200: OK401:invalid_api_key403:insufficient_scope404:not_found429:rate_limited500:internal_error
PATCH /v1/triggers/{id}
Change a trigger: send only the fields to change.
Scope: admin (keys that can manage) · SDK: punchline.updateTrigger()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | string (uuid) | yes | The trigger's ID. |
Idempotency-Key |
header | string | Retries with the same key and body return the first response. |
Body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ||
kind |
page_view, url_match, click, form_submit |
||
match |
string | A path (page_view, url_match with * wildcards) or a CSS selector (click, form_submit; needs the browser snippet) | |
event_name |
string | ||
active |
boolean |
Example
curl -X PATCH "https://app.punchlineanalytics.com/v1/triggers/92f7364b-89e3-43f0-aa7c-91954aa9b0af" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"match": "a.book-demo"
}'
Response 200:
{
"data": {
"id": "92f7364b-89e3-43f0-aa7c-91954aa9b0af",
"name": "Demo button",
"kind": "click",
"match": "a.book-demo",
"event_name": "demo_click",
"version": 2,
"active": true
}
}
Responses
200: OK400:invalid_json,invalid_parameter401:invalid_api_key403:insufficient_scope404:site_not_found,not_found413:body_too_large422:idempotency_key_reused429:rate_limited500:internal_error
DELETE /v1/triggers/{id}
Delete a trigger.
Scope: admin (keys that can manage) · SDK: punchline.deleteTrigger()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id |
path | string (uuid) | yes | The trigger's ID. |
Idempotency-Key |
header | string | Retries with the same key and body return the first response. |
Example
curl -X DELETE "https://app.punchlineanalytics.com/v1/triggers/92f7364b-89e3-43f0-aa7c-91954aa9b0af" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 204, no body.
Responses
204: Deleted401:invalid_api_key403:insufficient_scope404:not_found422:idempotency_key_reused429:rate_limited500:internal_error
GET /v1/url-parameters
The URL-parameter allowlist and rules. Needs the admin scope.
Scope: admin (keys that can manage) · SDK: punchline.getUrlParameters()
Example
curl "https://app.punchlineanalytics.com/v1/url-parameters" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 200:
{
"data": {
"params": [
{
"name": "ref",
"mode": "value"
},
{
"name": "gclid",
"mode": "presence"
}
],
"rules": []
}
}
Responses
200: OK401:invalid_api_key403:insufficient_scope429:rate_limited500:internal_error
PATCH /v1/url-parameters
Replace params and/or rules (send either or both). Rules may only use allowed parameters.
Scope: admin (keys that can manage) · SDK: punchline.updateUrlParameters()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | string | Retries with the same key and body return the first response. |
Body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
params |
list of object | ||
rules |
list of object |
Example
curl -X PATCH "https://app.punchlineanalytics.com/v1/url-parameters" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"params": [
{
"name": "ref",
"mode": "value"
},
{
"name": "gclid",
"mode": "presence"
}
]
}'
Response 200:
{
"data": {
"params": [
{
"name": "ref",
"mode": "value"
},
{
"name": "gclid",
"mode": "presence"
}
],
"rules": []
}
}
Responses
200: OK400:invalid_json,invalid_parameter401:invalid_api_key403:insufficient_scope404:site_not_found413:body_too_large422:idempotency_key_reused429:rate_limited500:internal_error
DELETE /v1/url-parameters
Allow no parameters (except utm_*) and remove all rules.
Scope: admin (keys that can manage) · SDK: punchline.deleteUrlParameters()
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key |
header | string | Retries with the same key and body return the first response. |
Example
curl -X DELETE "https://app.punchlineanalytics.com/v1/url-parameters" \
-H "Authorization: Bearer $PUNCHLINE_API_KEY"
Response 204, no body.
Responses
204: Cleared401:invalid_api_key403:insufficient_scope422:idempotency_key_reused429:rate_limited500:internal_error
Errors
Every error has one shape, and its docs_url links to the code's section below. param names the parameter at fault, when there is one; message never repeats what you sent.
{
"error": {
"type": "invalid_request_error",
"code": "invalid_parameter",
"message": "limit: must be at most 1000",
"param": "limit",
"docs_url": "https://app.punchlineanalytics.com/v1/errors#invalid_parameter"
}
}
| Code | HTTP | Type | Meaning | Retry? |
|---|---|---|---|---|
invalid_api_key |
401 | authentication_error |
Missing or invalid API key. Send it as: Authorization: Bearer pk_live_… | No: fix the request |
insufficient_scope |
403 | permission_error |
This API key doesn't have the scope this endpoint needs. | No: fix the request |
plan_upgrade_required |
403 | permission_error |
This site's plan doesn't include this. Upgrade in the dashboard under Billing. | No: fix the request |
not_found |
404 | invalid_request_error |
Not found. | No: fix the request |
site_not_found |
404 | invalid_request_error |
No site with that ID is available to this API key. | No: fix the request |
invalid_json |
400 | invalid_request_error |
The request body isn't valid JSON. | No: fix the request |
invalid_parameter |
400 | invalid_request_error |
A parameter is invalid. | No: fix the request |
body_too_large |
413 | invalid_request_error |
The request body is too large. | No: fix the request |
feature_unavailable |
409 | invalid_request_error |
This isn't available for this site. | No: fix the request |
idempotency_key_reused |
422 | invalid_request_error |
This Idempotency-Key was already used with a different request body. | No: fix the request |
rate_limited |
429 | rate_limit_error |
Too many requests. Retry after the time in the Retry-After header. | Yes, after Retry-After |
internal_error |
500 | api_error |
Something went wrong on our side. | Yes, with backoff |
invalid_api_key
HTTP 401. Check the header is exactly Authorization: Bearer pk_live_…, with no quotes or extra spaces, and that the key hasn't been revoked in Site settings → API and AI access.
insufficient_scope
HTTP 403. The endpoint needs a scope this key doesn't have (see Scope on each endpoint). Create a key with Can send events or Can manage ticked; scopes can't be added to an existing key.
plan_upgrade_required
HTTP 403. The site's plan doesn't include this endpoint: the API and MCP need Growth, and /v1/accounts and /v1/handoffs need Business. The message names the plan. The site's owner can upgrade under Billing in the dashboard; it takes effect within a minute (plans).
not_found
HTTP 404. The ID doesn't exist on this site, or it was deleted. IDs from another site always return this.
site_not_found
HTTP 404. site_id must be the site the key belongs to. Leave it out on GET endpoints to use the key's site; GET /v1/sites shows it.
invalid_json
HTTP 400. The body didn't parse. Send Content-Type: application/json and a JSON object; check for trailing commas and unescaped quotes.
invalid_parameter
HTTP 400. param names the field and message says what's wrong with it (allowed values, ranges or lengths). The same rules apply as in the dashboard.
body_too_large
HTTP 413. Bodies are capped at 64 KB (1 MB for POST /v1/events). Send events in batches of up to 1,000, or narrow the query.
feature_unavailable
HTTP 409. The report needs something this site hasn't set up yet: a connected GA4 property for GET /v1/reconciliation, or at least one goal for conversion metrics. The message says which; set it up in Site settings.
idempotency_key_reused
HTTP 422. An Idempotency-Key can only be reused with the identical body. Use a new key for a different request.
rate_limited
HTTP 429. 120 requests a minute per key. Wait for the Retry-After seconds; RateLimit-Remaining and RateLimit-Reset on every response let you pace before you hit it.
internal_error
HTTP 500. Our side failed. Retry with exponential backoff (writes are safe to retry with an Idempotency-Key). If it persists, tell us the time and endpoint.
Ask the docs
Answers come from these docs only, written by Claude (Anthropic). Your question is sent to answer it, and isn't stored.