What you can query
Eleven tables are available. The two you’ll reach for most:contact_360
One row per contact, already joining identity, on-chain metrics, activity, and email engagement, plus derived
churn_risk and days_since_last_activity.campaign_performance
One row per campaign with sends, opens, clicks, and rates pre-computed.
contacts, delivery_events, audience_profiles, audience_segments, campaigns, user_onchain_metrics, wallet_activity_summary, and nft_holdings. Full columns and types are in the SQL Schema Reference.
Start with contact_360 before hand-joining contacts to the metrics tables. It does that work for you and avoids row fan-out mistakes.
Rules
No semicolons, no SQL comments, no double-quoted identifiers. Joins, subqueries,
UNION, and window functions all work.
Two rules surprise people:
- Your query must start with
SELECT. A leadingWITHis rejected, wrap the CTE as a subquery instead. - The read-only check scans for keywords as plain text, so
WHERE first_name LIKE 'do %'trips it. See Common gotchas.
Questions worth asking
- Which wallets interacted with a contract recently but never converted?
- Which contacts clicked a campaign and then came back on-chain?
- Which high-value wallets have gone quiet in the last 30 days?
- Which holders of a collection have never received a message?
Workflow
1
Start from a starter query
Six starter queries cover the common shapes. Adapting one beats starting blank.
2
Narrow while iterating
Keep a tight date window and a small
LIMIT until the shape is right. The 5-second timeout is real.3
Validate a few rows by hand
Pick two or three wallets from the result and confirm they genuinely belong in the cohort.
4
Save as a report or segment
Saving as a segment requires a wallet column in your output,
wallet_address, wallet, or address. Without one, saving fails.5
Use it downstream
Feed the segment into a campaign or an automation.
Costs and caching
Running SQL is free, hand-written and saved queries never consume AI credits. Credits apply only when you generate SQL from a prompt. Identical queries re-run within 10 minutes return cached results without re-executing.Joining safely
Wallet casing is not normalized across tables. Always join withlower() on both sides:
delivery_eventshas nocampaign_idcolumn. Attribute events to a campaign viametadata->>'campaignId'. Thecampaign_run_idcolumn identifies a send run, not a campaign.wallet_activity_summaryis per wallet per chain, andnft_holdingsis per token. Joining either to a per-wallet table multiplies rows, aggregate or filter onchainfirst.

