punchline!

API

Limits and conventions

The rules every endpoint follows: dates, paging, rate limits, retries, sizes, freshness and privacy.

Requests and responses

  • Base URL: https://app.punchlineanalytics.com. All endpoints are under /v1.
  • HTTPS only, JSON in and out (Content-Type: application/json on bodies). Field names are snake_case.
  • Single results come back as {"data": {…}}, lists as {"data": […]}, plus next_cursor where a list pages. POST /v1/query is the exception: {"query", "results", "meta"}.
  • Report endpoints add dashboard_url: the same view in the dashboard, for linking people to it.
  • Errors always use one shape, with a code you can branch on.
  • Server-side only. The API sends no CORS headers, so browsers block calls from web pages. That's deliberate: a key in a web page is readable by anyone. Call the API from your servers.
  • Responses aren't cached (Cache-Control: no-store). Cache on your side if you poll.

Dates and times

  • Days are UTC and written YYYY-MM-DD. A range's to day is included: {"from": "2026-09-01", "to": "2026-09-30"} is all of September.
  • Report endpoints take from and to as query parameters. Leave out to for today, and from for 6 days before to (a week).
  • Timestamps are ISO 8601 in UTC, like 2026-10-06T14:50:00.000Z.
  • Today is partial: it includes traffic up to the last rollup refresh (below).

Data freshness and history

  • Totals come from rollups refreshed every 10 minutes. meta.rollups_refreshed_at says when.
  • Individual page-view records, and so agent sessions and their evidence, are kept for 90 days. Daily totals are kept, so queries over older ranges still work.
  • Labels are fixed when a request arrives. When Punchline learns about a new agent, it applies from then on; older traffic keeps the label it had.

Pagination

GET /v1/sessions returns the newest sessions first, limit at a time (1–100, default 25), with a next_cursor. Pass it back as cursor for the next page; it's null on the last page.

curl "https://app.punchlineanalytics.com/v1/sessions?limit=100" -H "Authorization: Bearer $PUNCHLINE_API_KEY"
# → {"data": [...], "next_cursor": "eyJl..."}
curl "https://app.punchlineanalytics.com/v1/sessions?limit=100&cursor=eyJl..." -H "Authorization: Bearer $PUNCHLINE_API_KEY"

Cursors are opaque: don't build or edit them. Keep the same filters while paging. The SDK does this for you with allSessions().

Other lists are bounded by limit instead. POST /v1/query returns up to 1,000 rows and says "truncated": true when there were more. For bigger pulls, use an export (up to 100,000 rows).

Rate limits

120 requests a minute for each key. Every response carries:

Header Meaning
RateLimit-Limit Requests allowed in the window (120)
RateLimit-Remaining Requests left in the current window
RateLimit-Reset Seconds until the window resets

Over the limit you get 429 rate_limited with Retry-After in seconds. Wait that long, then continue. The SDK does this automatically, twice by default.

To stay well under the limit, ask for more per request: one query with a day dimension instead of one query per day, and 100 sessions per page instead of 25.

Retries and idempotency

Reads are always safe to retry. For writes, send an Idempotency-Key header, any 1–128 visible ASCII characters you choose, such as a UUID or your own record's ID:

curl https://app.punchlineanalytics.com/v1/events \
  -H "Authorization: Bearer $PUNCHLINE_API_KEY" \
  -H "Idempotency-Key: order-10482" \
  -H "Content-Type: application/json" \
  -d '{"site_id": "…", "events": [{"name": "purchase", "value": 249}]}'
  • A retry with the same key and the same body gets the first response back, and nothing is written twice.
  • The same key with a different body is refused with 422 idempotency_key_reused.
  • Keys are remembered for at least 24 hours, separately for each API key.
  • Supported on POST /v1/events and on every create, change and delete under alerts, goals, triggers and URL parameters.

Retry 429 after Retry-After, and 500 with exponential backoff (1 s, 2 s, 4 s…). Don't retry other 4xx errors: the request needs fixing first.

Sizes

Limit Value
Request body 64 KB
POST /v1/events body 1 MB, up to 1,000 events
Event age Within the last 36 hours (and not more than 5 minutes ahead)
POST /v1/query rows 1,000 per request
Export rows 100,000; files up to 4 MB compressed, kept 24 hours
Handoff link lifetime 1–90 days (default 30)

A body over its limit gets 413 body_too_large.

Privacy thresholds

Everything outside your own dashboard (the API, MCP, exports, warehouse tables and share links) follows the same rules:

  • Groups with fewer than 5 people's page views are withheld and counted in meta.suppressed_rows (or withheld_people in reports). This stops a narrow query from describing one person.
  • Sessions of people are never returned. Agent and crawler sessions are, in full.
  • Agent-only groups are always shown, however small.

If a number you expect is missing, the group was probably small. Drop a dimension or widen the date range, and the people are counted in the larger group.

Versioning

The API is versioned in the path (/v1). Within v1 we add endpoints, fields and dimension values without notice, so ignore fields you don't recognize and don't fail on new enum values. Anything that would break existing code goes in a new version. Changes are listed in the changelog, and the OpenAPI spec always matches the API that's running.

Ask the docs

Answers come from these docs only, written by Claude (Anthropic). Your question is sent to answer it, and isn't stored.