Integrations
BigQuery and Snowflake
Get Punchline's numbers into your warehouse every day, next to the rest of your data, with no API code to maintain. Each morning Punchline writes the previous UTC day into four tables in a BigQuery dataset or Snowflake schema you choose.
Set it up in Site settings → Warehouse export. You need to be an owner or admin of the site.
What's written
Four tables, one schema everywhere (the same as exports):
| Table | One row per |
|---|---|
punchline_traffic_daily |
Day × page × visitor type × agent and source |
punchline_conversions_daily |
Day × goal × who converted × channel × landing page |
punchline_events_daily |
Day × custom event × who sent it |
punchline_agent_sessions |
Agent or crawler session, with its pages, rules and evidence |
The same privacy rules apply as for the API: groups with fewer than 5 people's page views are withheld, and people's sessions are never exported. Agent sessions are exported in full.
In Snowflake, table and column names are upper case (PUNCHLINE_TRAFFIC_DAILY.SESSIONS).
When it runs
- Every day at about 08:00 UTC, for the previous UTC day.
- Each day replaces that day's rows (BigQuery replaces the day's partition; Snowflake deletes the day, then inserts), so a rerun never duplicates anything.
- Missed days are caught up, up to 7 days back. If a run fails, the error shows next to the destination, and the next run starts from the first missing day.
- Sync now writes the missing days immediately. Test checks the connection without writing.
- Pause stops the daily run without removing the destination.
BigQuery
Punchline writes with its own service account, which you give access to one dataset only. No key or password is exchanged.
- In BigQuery, create a dataset for Punchline (for example
punchline), in the location you want. - Open the dataset → Sharing → Permissions → Add principal. Add the service account shown in Punchline's Warehouse export card, with the role BigQuery Data Editor. Grant it on the dataset, not the project, so Punchline can't see anything else.
- Open the dataset → Edit details → Labels → Add label. Set the key to
punchline-siteand the value to your site ID (shown in the Warehouse export card). Every Punchline customer shares datasets with the same service account, so the label is how Punchline knows the dataset is yours: only someone who can edit the dataset can set it, and Punchline never sets labels itself. - In Punchline, enter the project ID and dataset ID and press Connect BigQuery. Punchline checks it can see the dataset and that the label matches before saving, and checks the label again before every daily sync. If the label is removed or changed, syncing stops and the card shows why.
Tables are partitioned by day, so filter on day to keep queries cheap. Load jobs run in Punchline's project. You pay BigQuery's usual rates for storing the tables and for the queries you run on them.
Snowflake
Punchline signs in as a service user with a key pair it creates for your destination. You run one script to create that user.
- In Punchline, press Add Snowflake and enter:
- Account identifier, like
myorg-myaccount(from Snowflake's account menu → View account details). - User to create (default
PUNCHLINE), Database, Schema (defaultPUNCHLINE), Warehouse (a small one is plenty), and optionally a Role (defaultPUNCHLINE_WRITER).
- Account identifier, like
- Punchline shows the setup SQL. Run it once in a Snowflake worksheet as a role that can create users and grant privileges, such as
SECURITYADMIN. It creates the role and the service user, grants the role use of the warehouse and database and permission to create tables in the schema, and sets Punchline's public key on the user. - Press Test, then Sync now.
Loading runs on the warehouse you named, for a few seconds a day, and Snowflake bills that time as usual. An X-Small warehouse with auto-suspend is plenty.
The private key is generated by Punchline, stored encrypted, and never shown or sent anywhere except to sign Snowflake logins. To revoke access, drop the user or unset its public key in Snowflake, then remove the destination.
The script looks like this, with your names:
-- Run as a role that can create users and grant privileges (e.g. SECURITYADMIN).
CREATE ROLE IF NOT EXISTS PUNCHLINE_WRITER;
GRANT USAGE ON WAREHOUSE LOADING_WH TO ROLE PUNCHLINE_WRITER;
GRANT USAGE ON DATABASE ANALYTICS TO ROLE PUNCHLINE_WRITER;
GRANT USAGE, CREATE TABLE ON SCHEMA ANALYTICS.PUNCHLINE TO ROLE PUNCHLINE_WRITER;
CREATE USER IF NOT EXISTS PUNCHLINE TYPE = SERVICE DEFAULT_ROLE = PUNCHLINE_WRITER DEFAULT_WAREHOUSE = LOADING_WH;
ALTER USER PUNCHLINE SET RSA_PUBLIC_KEY = 'MIIBIjANBgkqh…';
GRANT ROLE PUNCHLINE_WRITER TO USER PUNCHLINE;
Example queries
Agent share by week (BigQuery):
SELECT
DATE_TRUNC(day, WEEK(MONDAY)) AS week,
SUM(IF(visitor_type IN ('agent', 'crawler'), pageviews, 0)) / SUM(pageviews) AS agent_share
FROM punchline.punchline_traffic_daily
WHERE day >= DATE_SUB(CURRENT_DATE(), INTERVAL 12 WEEK)
GROUP BY week
ORDER BY week;
Pages each AI company read most in the last 30 days (Snowflake):
SELECT operator, f.value::string AS path, COUNT(*) AS sessions
FROM PUNCHLINE.PUNCHLINE_AGENT_SESSIONS, LATERAL FLATTEN(input => PARSE_JSON(PATHS)) f
WHERE DAY >= DATEADD(day, -30, CURRENT_DATE())
GROUP BY 1, 2
ORDER BY sessions DESC
LIMIT 50;
Conversions by people sent from AI assistants, joined to your own revenue table (BigQuery):
SELECT c.day, SUM(c.conversions) AS ai_referred_conversions, ANY_VALUE(r.revenue) AS revenue
FROM punchline.punchline_conversions_daily c
LEFT JOIN finance.daily_revenue r USING (day)
WHERE c.visitor_type = 'ai_referred' AND c.goal = 'Purchase'
GROUP BY c.day
ORDER BY c.day;
Columns
Generated from the export's own table definitions.
punchline_traffic_daily
Sessions and page views per day, page, visitor type and source. Groups with fewer than 5 people's page views are withheld.
| Column | BigQuery type | Snowflake type | Description |
|---|---|---|---|
day |
DATE | DATE | UTC day |
path |
STRING | VARCHAR | Page path |
visitor_type |
STRING | VARCHAR | human, ai_referred, agent or crawler (at the likely confidence) |
actor_class |
STRING | VARCHAR | Punchline's label for the visitor |
operator |
STRING | VARCHAR | The company behind an agent or crawler |
tier |
STRING | VARCHAR | How sure the label is: verified, declared, inferred_high, inferred_low, none |
channel |
STRING | VARCHAR | How the visit arrived |
referrer_host |
STRING | VARCHAR | Referring site |
utm_source |
STRING | VARCHAR | utm_source |
utm_medium |
STRING | VARCHAR | utm_medium |
utm_campaign |
STRING | VARCHAR | utm_campaign |
sessions |
INT64 | NUMBER(38,0) | Sessions that started on this page |
pageviews |
INT64 | NUMBER(38,0) | Page views |
punchline_conversions_daily
Goal conversions per day, credited to how the session arrived.
| Column | BigQuery type | Snowflake type | Description |
|---|---|---|---|
day |
DATE | DATE | UTC day |
goal |
STRING | VARCHAR | Goal name |
visitor_type |
STRING | VARCHAR | Who converted |
channel |
STRING | VARCHAR | How the session arrived |
landing_path |
STRING | VARCHAR | First page of the session |
conversions |
INT64 | NUMBER(38,0) | Sessions that completed the goal |
punchline_events_daily
Custom events per day.
| Column | BigQuery type | Snowflake type | Description |
|---|---|---|---|
day |
DATE | DATE | UTC day |
event_name |
STRING | VARCHAR | Event name |
visitor_type |
STRING | VARCHAR | Who sent it (null for server events not tied to a visit) |
events |
INT64 | NUMBER(38,0) | Events |
event_value |
FLOAT64 | FLOAT | Sum of event values |
punchline_agent_sessions
Every agent and crawler session, with the rules and evidence behind its label. People's sessions are never exported.
| Column | BigQuery type | Snowflake type | Description |
|---|---|---|---|
day |
DATE | DATE | UTC day |
session_id |
STRING | VARCHAR | Session ID |
started_at |
TIMESTAMP | TIMESTAMP_TZ | First request |
ended_at |
TIMESTAMP | TIMESTAMP_TZ | Last request |
actor_class |
STRING | VARCHAR | Label |
operator |
STRING | VARCHAR | Operator |
tier |
STRING | VARCHAR | Confidence tier |
pageviews |
INT64 | NUMBER(38,0) | Requests in the session |
paths |
JSON | VARCHAR (JSON text) | Paths in request order (up to 200) |
rule_ids |
JSON | VARCHAR (JSON text) | Classifier rules that fired |
evidence |
JSON | VARCHAR (JSON text) | The evidence behind the label |
Other ways to get data out
POST /v1/exports: a CSV or Parquet file of any query, up to 100,000 rows, when you need it.- The API and the query cookbook, for pulling exactly the numbers you want from your own code or a BI tool.
Ask the docs
Answers come from these docs only, written by Claude (Anthropic). Your question is sent to answer it, and isn't stored.