Chartsy Integrations API reference
The Integrations API is the read-only JSON API behind Chartsy's Zapier app and other automation platforms. It gives authorized apps your MRR, revenue, churn, cancellations, goals and website-traffic figures as flat, mappable fields, plus REST hooks for instant triggers.
Overview#
| Base URL | https://dashboard.chartsy.app/api/integrations/v1/ |
| Format | JSON responses (UTF-8). POST bodies as JSON. |
| Authentication | OAuth 2.0 authorization code with PKCE, bearer access tokens |
| Scope | chartsy:read |
| Versioning | The version is in the path (v1). Fields may be added to v1; none are removed or renamed. |
| OpenAPI | chartsy-integrations-v1.json |
The API reads data Chartsy has already synced from Stripe, Paddle, Freemius, Polar.sh, Creem.io and Lemon Squeezy, plus Chartsy website tracking. It never writes to your billing provider. The only things it stores are REST hook subscriptions, which an app creates and deletes for its instant triggers.
The API has three kinds of endpoints. Feeds (/events/…) return newest-first lists where every item has a stable id, for polling triggers. Hooks (/hooks/) register a callback URL for an instant trigger. Reports (/reports/…) return one object, for "get" actions and searches.
Authentication#
Every request needs an OAuth 2.0 access token in the Authorization header:
curl https://dashboard.chartsy.app/api/integrations/v1/me/ \
-H "Authorization: Bearer <access_token>"Tokens are issued to registered integration clients (Zapier and other automation platforms) through the authorization code flow. To register a client, contact support@chartsy.app with your platform name and redirect URI. Tokens issued to other Chartsy clients, such as MCP connectors, are not accepted here.
| Step | Endpoint |
|---|---|
| Authorize (browser) | GET https://dashboard.chartsy.app/o/authorize/ |
| Exchange code / refresh | POST https://dashboard.chartsy.app/o/token/ |
| Revoke a token | POST https://dashboard.chartsy.app/o/revoke_token/ |
1. Send the user to authorize
https://dashboard.chartsy.app/o/authorize/
?response_type=code
&client_id=<client_id>
&redirect_uri=<registered redirect URI>
&scope=chartsy:read
&state=<random>
&code_challenge=<BASE64URL(SHA256(code_verifier))>
&code_challenge_method=S256The user signs in to Chartsy and approves the app. Chartsy redirects back to your redirect URI with code and state. PKCE is required.
2. Exchange the code
curl -X POST https://dashboard.chartsy.app/o/token/ \
-H "Content-Type: application/x-www-form-urlencoded" \
-d grant_type=authorization_code \
-d code=<code> \
-d redirect_uri=<registered redirect URI> \
-d code_verifier=<code_verifier> \
-d client_id=<client_id> \
-d client_secret=<client_secret>{
"access_token": "Yp4…",
"expires_in": 3600,
"token_type": "Bearer",
"scope": "chartsy:read",
"refresh_token": "kX9…"
}3. Refresh before or when the token expires
curl -X POST https://dashboard.chartsy.app/o/token/ \
-H "Content-Type: application/x-www-form-urlencoded" \
-d grant_type=refresh_token \
-d refresh_token=<refresh_token> \
-d client_id=<client_id> \
-d client_secret=<client_secret>- Access tokens
- Valid for 1 hour. An expired token gets 401 with error="invalid_token".
- Refresh tokens
- Valid for 90 days and rotated on every use: each refresh returns a new refresh_token, which you must store in place of the old one. Reusing the old one is accepted for 2 minutes (so two parallel refreshes both succeed), then it returns 400 invalid_grant.
- Client authentication
- Send client_id and client_secret in the form body or as HTTP Basic auth. A refresh without them returns 401 invalid_client.
Access and errors#
A token works while the Chartsy login has a connected payment provider and an active plan or free trial. This is checked on every request, so a token issued during a trial stops reading data when the trial ends, and starts again once a plan is picked. No reconnect is needed.
| Status | Body | Meaning |
|---|---|---|
| 200 / 201 | The resource | Success. 201 is returned when a hook is created. |
| 400 | {"error": "…"} | A parameter is invalid, or the login has several accounts and account_id is missing. The message is written for the end user. |
| 401 | {"detail": "…"} | Missing, expired or revoked token. Refresh the token and retry. A WWW-Authenticate header gives the reason. |
| 403 | {"detail": "…"} | The token is valid but the login cannot use integrations right now (trial ended, no payment provider), or the token belongs to a non-integration client. |
| 404 | {"error": "No such hook."} | The hook does not exist or belongs to another login. |
| 500 | {"error": "…"} | Chartsy failed to complete the request. The failure is logged; retry later. |
There are no per-request charges and no fixed rate limit. Please poll feeds no more often than once a minute; their responses are cached for up to 15 minutes anyway.
Conventions#
- Accounts
- A Chartsy login can own several accounts (one per connected billing source). Endpoints that read billing data take an optional
account_id. With one account it can be left out; with several, it is required, and the 400 message says so. Only the caller's own active accounts are visible; demo accounts are never returned. - Money
- Numbers (not strings) in the account currency, given in the currency field.
- Dates and times
- Dates are YYYY-MM-DD. Timestamps are ISO 8601 in UTC. Date parameters also accept a full timestamp; only the date part is used.
- Feed order and ids
- Feeds return newest (or highest) first, except at-risk customers, listed paused first and then failed payments. Each item has an id that never changes, so a poller can de-duplicate on it.
- summary
- Most objects include a ready-written summary string for posting to Slack, email and similar.
- Caching
- Responses are cached per login and parameters: feeds 15 minutes, reports and lists 5 minutes. GET /events/cancellations/ and the hook endpoints are not cached.
- Customer data
- Feeds and reports identify customers by the billing provider's ids. The cancellation event is the exception: it includes the customer's name and email so someone can follow up.
Connection and dropdowns#
GET/me/
email as the connection label.Example response
{
"id": 171,
"email": "founder@example.com"
}GET/accounts/
id is the account_id other endpoints take.| Field | Type | Description |
|---|---|---|
id | integer | Account id. |
name | string | Display name. |
currency | string | Default currency, ISO 4217. |
payment_processor | string | null | Connected billing provider, e.g. Stripe. |
Example response
[
{
"id": 4776,
"name": "Acme (Stripe)",
"currency": "USD",
"payment_processor": "Stripe"
}
]GET/sites/
id is the site_id other endpoints take.Example response
[
{
"id": 12,
"name": "acme.com"
}
]Feeds (polling triggers)#
Each feed returns a JSON array. Poll it and treat any item whose id you have not seen as new.
GET/events/daily-snapshots/
"<account_id>:<date>". A day counts as settled once it has ended and the 02:00 UTC syncs have run. Its figures are computed once and frozen, so later polls always return the same id and values. Covers all tracked sites. Fields: Daily summary.Parameters
| Field | Type | Description |
|---|---|---|
account_id | integer, optional | Which of your accounts to read (from GET /accounts/). Required only when the login has more than one account. |
Example response
[
{
"id": "4776:2026-10-16",
"date": "2026-10-16",
"account_id": 4776,
"account_name": "Acme (Stripe)",
"currency": "USD",
"mrr": 4250,
"revenue": 312.5,
"revenue_previous_day": 198,
"revenue_trailing_daily_average": 240.12,
"revenue_change_vs_previous_day_percent": 57.8,
"revenue_change_vs_trailing_average_percent": 30.1,
"new_subscribers": 3,
"new_subscribers_previous_day": 1,
"churned_customers": 0,
"churn_rate_percent": 0,
"refund_count": 0,
"total_refunded": 0,
"visits": 1820,
"unique_visitors": 1410,
"signups": 22,
"visits_change_vs_trailing_average_percent": 12.4,
"top_source": "google",
"warning_count": 0,
"warnings": "",
"summary": "Chartsy daily summary for 2026-10-16 (Acme (Stripe))\nRevenue: 312.50 USD (+30.1%) vs 7-day average\nMRR: 4,250.00 USD\nNew subscribers: 3 - churned: 0 - refunds: 0\nVisits: 1820 - signups: 22",
"revenue_by_plan": [
{
"plan": "Pro",
"revenue": 312.5
}
],
"traffic_by_source": [
{
"source": "google",
"visits": 940,
"signups": 12
}
]
}
]GET/events/daily-summaries/
"<account_id>-<date>". Computed live, with an optional site filter. Fields: Daily summary.Parameters
| Field | Type | Description |
|---|---|---|
account_id | integer, optional | Which of your accounts to read (from GET /accounts/). Required only when the login has more than one account. |
site_id | integer, optional | Limit traffic figures to one tracked site (from GET /sites/). Omit for all sites. |
Example response
[
{
"id": "4776-2026-10-16",
"date": "2026-10-16",
"account_id": 4776,
"account_name": "Acme (Stripe)",
"currency": "USD",
"mrr": 4250,
"revenue": 312.5,
"revenue_previous_day": 198,
"revenue_trailing_daily_average": 240.12,
"revenue_change_vs_previous_day_percent": 57.8,
"revenue_change_vs_trailing_average_percent": 30.1,
"new_subscribers": 3,
"new_subscribers_previous_day": 1,
"churned_customers": 0,
"churn_rate_percent": 0,
"refund_count": 0,
"total_refunded": 0,
"visits": 1820,
"unique_visitors": 1410,
"signups": 22,
"visits_change_vs_trailing_average_percent": 12.4,
"top_source": "google",
"warning_count": 0,
"warnings": "",
"summary": "Chartsy daily summary for 2026-10-16 (Acme (Stripe))\nRevenue: 312.50 USD (+30.1%) vs 7-day average\nMRR: 4,250.00 USD\nNew subscribers: 3 - churned: 0 - refunds: 0\nVisits: 1820 - signups: 22",
"revenue_by_plan": [
{
"plan": "Pro",
"revenue": 312.5
}
],
"traffic_by_source": [
{
"source": "google",
"visits": 940,
"signups": 12
}
]
}
]GET/events/cancellations/
limit, the list is topped up with the account's most recent canceled subscriptions. Those have id "preview-<subscription_id>" and are never delivered. Not cached. Fields: Cancellation.Parameters
| Field | Type | Description |
|---|---|---|
account_id | integer, optional | Which of your accounts to read (from GET /accounts/). Required only when the login has more than one account. |
limit | integer, optional | Items to return, 1-100. Default 25. |
Example response
[
{
"id": 123,
"account_id": 4776,
"provider": "stripe",
"subscription_id": "sub_1Q2w3E4r5T6y",
"customer_id": "cus_Q1w2e3r4",
"customer_name": "Alex Smith",
"customer_email": "alex@example.com",
"plan_name": "Growth",
"mrr_at_risk": 49,
"currency": "USD",
"canceled_at": "2026-10-24T18:00:00+00:00",
"access_ends_at": "2026-11-24T18:00:00+00:00",
"cancellation_reason": "Too expensive",
"summary": "Alex Smith canceled Growth (49.00 USD/month at risk) - access ends 2026-11-24. Reason: Too expensive"
}
]GET/events/goals-reached/
id is the goal id.| Field | Type | Description |
|---|---|---|
id | integer | Goal id. |
title | string | Goal title, or a generated one. |
goal_type | string | mrr, arr, total_subscribers or churn_rate. |
goal_type_label | string | Readable goal type. |
target_value | number | Target. |
baseline_value | number | null | Value when the goal was set. |
deadline | date | null | Goal deadline. |
achieved_at | date | When the goal was reached. |
account_id / account_name / currency | The account it belongs to. | |
summary | string | Ready-to-post sentence. |
Parameters
| Field | Type | Description |
|---|---|---|
account_id | integer, optional | Which of your accounts to read (from GET /accounts/). Required only when the login has more than one account. |
Example response
[
{
"id": 52,
"title": "Monthly Recurring Revenue target of 5,000.00",
"goal_type": "mrr",
"goal_type_label": "Monthly Recurring Revenue",
"target_value": 5000,
"baseline_value": 3800,
"deadline": "2026-10-31",
"achieved_at": "2026-10-16",
"account_id": 4776,
"account_name": "Acme (Stripe)",
"currency": "USD",
"summary": "Goal reached: Monthly Recurring Revenue target of 5,000.00 (Acme (Stripe))"
}
]GET/events/mrr-milestones/
"<account_id>-mrr-<milestone>", so each milestone is new exactly once, the first time MRR passes it.Parameters
| Field | Type | Description |
|---|---|---|
account_id | integer, optional | Which of your accounts to read (from GET /accounts/). Required only when the login has more than one account. |
Example response
[
{
"id": "4776-mrr-5000",
"milestone": 5000,
"mrr": 5012.4,
"currency": "USD",
"account_id": 4776,
"account_name": "Acme (Stripe)",
"summary": "Acme (Stripe) passed 5,000 USD MRR (now 5,012.40)"
}
]GET/events/at-risk-customers/
paused-<subscription_id>) and customers with 2 or more failed payments this quarter (id failed-<customer_id>-<quarter>).| Field | Type | Description |
|---|---|---|
id | string | Stable id, see above. |
reason | string | paused or failed_payments. |
customer_id | string | The provider's customer id. |
subscription_id | string | null | Set for paused subscriptions. |
failed_payments_this_quarter | integer | null | Set for failed_payments. |
paused_date | date | null | Set for paused. |
days_paused | integer | null | Set for paused. |
account_id / account_name | The account it belongs to. | |
summary | string | Ready-to-post sentence. |
Parameters
| Field | Type | Description |
|---|---|---|
account_id | integer, optional | Which of your accounts to read (from GET /accounts/). Required only when the login has more than one account. |
Example response
[
{
"id": "failed-cus_Q1w2e3r4-2026Q4",
"reason": "failed_payments",
"customer_id": "cus_Q1w2e3r4",
"subscription_id": null,
"failed_payments_this_quarter": 2,
"paused_date": null,
"days_paused": null,
"account_id": 4776,
"account_name": "Acme (Stripe)",
"summary": "Customer cus_Q1w2e3r4 has 2 failed payments this quarter (Acme (Stripe))"
}
]REST hooks (instant triggers)#
An app subscribes a callback URL to one event on one account. Chartsy then POSTs each new event there as it happens. The only event today is subscription_canceled: a customer cancels, or schedules a cancel at the end of the period, in their billing provider.
POST/hooks/
id to unsubscribe later. A login can have at most 100 active hooks.curl -X POST https://dashboard.chartsy.app/api/integrations/v1/hooks/ \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{"event": "subscription_canceled", "account_id": 4776,
"target_url": "https://hooks.zapier.com/hooks/standard/1234567/abcdef/"}'Parameters
| Field | Type | Description |
|---|---|---|
event | string, required | subscription_canceled |
target_url | string, required | The callback URL. https on port 443, resolving to a public address. At most 2000 characters. |
account_id | integer, optional | Which of your accounts to read (from GET /accounts/). Required only when the login has more than one account. |
Example response
{
"id": 88,
"account_id": 4776,
"event": "subscription_canceled",
"target_url": "https://hooks.zapier.com/hooks/standard/1234567/abcdef/",
"active": true,
"created_at": "2026-10-24T18:05:11.402913+00:00"
}DELETE/hooks/{id}/
active: false.Example response
{
"id": 88,
"account_id": 4776,
"event": "subscription_canceled",
"target_url": "https://hooks.zapier.com/hooks/standard/1234567/abcdef/",
"active": false,
"created_at": "2026-10-24T18:05:11.402913+00:00"
}GET/hooks/{id}/
Example response
{
"id": 88,
"account_id": 4776,
"event": "subscription_canceled",
"target_url": "https://hooks.zapier.com/hooks/standard/1234567/abcdef/",
"active": true,
"created_at": "2026-10-24T18:05:11.402913+00:00"
}GET/hooks/
Example response
[
{
"id": 88,
"account_id": 4776,
"event": "subscription_canceled",
"target_url": "https://hooks.zapier.com/hooks/standard/1234567/abcdef/",
"active": true,
"created_at": "2026-10-24T18:05:11.402913+00:00"
}
]Webhook delivery#
For each new cancellation, Chartsy sends one POST to every active hook on that account, usually within seconds of the billing provider notifying Chartsy:
POST <target_url>
Content-Type: application/json
User-Agent: Chartsy-Webhooks/1.0 (+https://chartsy.app)
{
"id": 123,
"account_id": 4776,
"provider": "stripe",
"subscription_id": "sub_1Q2w3E4r5T6y",
"customer_id": "cus_Q1w2e3r4",
"customer_name": "Alex Smith",
"customer_email": "alex@example.com",
"plan_name": "Growth",
"mrr_at_risk": 49,
"currency": "USD",
"canceled_at": "2026-10-24T18:00:00+00:00",
"access_ends_at": "2026-11-24T18:00:00+00:00",
"cancellation_reason": "Too expensive",
"summary": "Alex Smith canceled Growth (49.00 USD/month at risk) - access ends 2026-11-24. Reason: Too expensive"
}The body is a Cancellation object.
- What counts as a cancellation
- A subscription Chartsy already knew about that changes from live to canceled or scheduled to cancel. Repeated or out-of-order provider notifications of the same cancel produce one event. Resuming and canceling again produces a new event. Importing an account, or re-syncing old history, never sends past cancellations.
- Success
- Any 2xx response. The response body is ignored.
- Retries
- Network errors, timeouts (10 seconds), 429 and 5xx are retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, always with the same event id so the receiver can drop duplicates.
- 410 Gone
- Chartsy switches the hook off and sends nothing more to it.
- Other 4xx
- The delivery is marked failed and not retried.
- Redirects
- Not followed. 3xx counts as a failure.
- URL safety
- Target URLs are checked when the hook is created and before every delivery: https only, standard port, no credentials, and every resolved address must be public.
Reports#
Each report returns a single JSON object.
GET/reports/mrr/
Parameters
| Field | Type | Description |
|---|---|---|
account_id | integer, optional | Which of your accounts to read (from GET /accounts/). Required only when the login has more than one account. |
Example response
{
"id": "4776-mrr-2026-10-17",
"account_id": 4776,
"account_name": "Acme (Stripe)",
"mrr": 4250,
"arr": 51000,
"currency": "USD",
"as_of": "2026-10-17"
}GET/reports/daily-summary/
Parameters
| Field | Type | Description |
|---|---|---|
account_id | integer, optional | Which of your accounts to read (from GET /accounts/). Required only when the login has more than one account. |
date | date, optional | Day to summarize, YYYY-MM-DD. Defaults to yesterday. |
site_id | integer, optional | Limit traffic figures to one tracked site (from GET /sites/). Omit for all sites. |
Example response
{
"id": "4776-2026-10-16",
"date": "2026-10-16",
"account_id": 4776,
"account_name": "Acme (Stripe)",
"currency": "USD",
"mrr": 4250,
"revenue": 312.5,
"revenue_previous_day": 198,
"revenue_trailing_daily_average": 240.12,
"revenue_change_vs_previous_day_percent": 57.8,
"revenue_change_vs_trailing_average_percent": 30.1,
"new_subscribers": 3,
"new_subscribers_previous_day": 1,
"churned_customers": 0,
"churn_rate_percent": 0,
"refund_count": 0,
"total_refunded": 0,
"visits": 1820,
"unique_visitors": 1410,
"signups": 22,
"visits_change_vs_trailing_average_percent": 12.4,
"top_source": "google",
"warning_count": 0,
"warnings": "",
"summary": "Chartsy daily summary for 2026-10-16 (Acme (Stripe))\nRevenue: 312.50 USD (+30.1%) vs 7-day average\nMRR: 4,250.00 USD\nNew subscribers: 3 - churned: 0 - refunds: 0\nVisits: 1820 - signups: 22",
"revenue_by_plan": [
{
"plan": "Pro",
"revenue": 312.5
}
],
"traffic_by_source": [
{
"source": "google",
"visits": 940,
"signups": 12
}
]
}GET/reports/business-snapshot/
Parameters
| Field | Type | Description |
|---|---|---|
account_id | integer, optional | Which of your accounts to read (from GET /accounts/). Required only when the login has more than one account. |
months | integer, optional | Look-back window, 1-36. Default 12. |
site_id | integer, optional | Limit traffic figures to one tracked site (from GET /sites/). Omit for all sites. |
Example response
{
"id": "4776-snapshot-2026-10-17",
"account_id": 4776,
"account_name": "Acme (Stripe)",
"currency": "USD",
"start_date": "2025-11-01",
"end_date": "2026-10-17",
"mrr": 4250,
"new_subscribers_this_month": 18,
"churn_rate_this_month_percent": 2.1,
"top_customers_share_of_mrr_percent": 31.5,
"refund_count": 4,
"total_refunded": 196,
"at_risk_failed_payments": 1,
"at_risk_paused": 0,
"warning_count": 0,
"warnings": "",
"summary": "Chartsy business snapshot (Acme (Stripe), last 12 months)\nMRR: 4,250.00 USD",
"mrr_by_plan": [
{
"plan": "Pro",
"period": "2026-10-01",
"mrr": 4250,
"customers": 52
}
],
"traffic_by_source": [
{
"source": "google",
"visits": 24100,
"signups": 310
}
]
}GET/reports/revenue-by-source/
touch) and followed into billing. low_sample marks groups with fewer than 5 paying customers; notes lists caveats, one per line. Money fields are null when the login's billing data can't be joined.Parameters
| Field | Type | Description |
|---|---|---|
start_date | date, optional | First day included, YYYY-MM-DD. Defaults to 30 days ago. |
end_date | date, optional | Last day included, YYYY-MM-DD. Defaults to today. |
by | string, optional | source (default), medium, campaign or content. |
touch | string, optional | last (default) or first: which visit gets the credit. |
site_id | integer, optional | Limit traffic figures to one tracked site (from GET /sites/). Omit for all sites. |
Example response
{
"id": "revenue-by-source-last-2026-09-17-2026-10-17",
"by": "source",
"touch": "last",
"start_date": "2026-09-17",
"end_date": "2026-10-17",
"currency": "USD",
"signups": 140,
"unattributed_paying_customers": 2,
"top_group": "google",
"top_group_mrr": 610,
"top_group_paying_customers": 9,
"summary": "Revenue by source (last touch), 2026-09-17 to 2026-10-17\ngoogle: 9 paying, MRR 610.00 USD",
"notes": "low_sample is true below 5 paying customers - treat those groups' rates as inconclusive.",
"groups": [
{
"group": "google",
"signups": 80,
"matched_signups": 30,
"unmatched_signups": 50,
"paying_customers": 9,
"signup_to_paid_rate_percent": 11.25,
"current_mrr": 610,
"active_customers": 9,
"churned_customers": 0,
"churned_mrr": 0,
"retention_rate_percent": 100,
"low_sample": false
}
]
}GET/reports/traffic-sources/
Parameters
| Field | Type | Description |
|---|---|---|
start_date | date, optional | First day included, YYYY-MM-DD. Defaults to 30 days ago. |
end_date | date, optional | Last day included, YYYY-MM-DD. Defaults to today. |
by | string, optional | source (default), medium, campaign, content, country, referrer or landing_path. |
site_id | integer, optional | Limit traffic figures to one tracked site (from GET /sites/). Omit for all sites. |
Example response
{
"id": "traffic-by-source-2026-09-17-2026-10-17",
"by": "source",
"start_date": "2026-09-17",
"end_date": "2026-10-17",
"visits": 52000,
"signups": 640,
"top_group": "google",
"top_group_visits": 24100,
"summary": "Traffic by source, 2026-09-17 to 2026-10-17\ngoogle: 24100 visits, 310 signups",
"groups": [
{
"group": "google",
"visits": 24100,
"unique_visitors": 19800,
"signups": 310,
"registered_accounts": 40,
"signup_rate_percent": 1.29
}
]
}Object reference#
Daily summary
Returned by /events/daily-snapshots/, /events/daily-summaries/ and /reports/daily-summary/.
| Field | Type | Description |
|---|---|---|
id | string | Stable id: "<account_id>-<date>" on /events/daily-summaries/, "<account_id>:<date>" on /events/daily-snapshots/. |
date | date | The day the figures cover (UTC). |
account_id | integer | Chartsy account id. |
account_name | string | Account display name. |
currency | string | ISO 4217 code of every money field. |
mrr | number | Monthly recurring revenue at the end of the day. |
revenue | number | Revenue collected that day. |
revenue_previous_day | number | Revenue the day before. |
revenue_trailing_daily_average | number | Average daily revenue over the previous 7 days. |
revenue_change_vs_previous_day_percent | number | null | Change vs the previous day. Null when there is nothing to compare against. |
revenue_change_vs_trailing_average_percent | number | null | Change vs the 7-day average. |
new_subscribers | integer | New paying subscribers that day. |
new_subscribers_previous_day | integer | New paying subscribers the day before. |
churned_customers | integer | Customers whose paid access ended that day. |
churn_rate_percent | number | null | Customer churn rate for the day. |
refund_count | integer | Refunds issued that day. |
total_refunded | number | Amount refunded that day. |
visits | integer | null | Website visits. Null when website tracking is not installed. |
unique_visitors | integer | null | Unique visitors. |
signups | integer | null | Tracked signups. |
visits_change_vs_trailing_average_percent | number | null | Visits change vs the 7-day average. |
top_source | string | null | Traffic source with the most visits. |
warning_count | integer | Number of data-quality warnings about these figures. |
warnings | string | The warnings, one per line. Empty when there are none. |
summary | string | Ready-to-post multi-line text summary. |
revenue_by_plan | array | Line items: { plan, revenue }. |
traffic_by_source | array | Up to 10 line items: { source, visits, signups }. |
Cancellation
Delivered to subscription_canceled hooks and returned by /events/cancellations/.
| Field | Type | Description |
|---|---|---|
id | integer | string | Event id; the same on every delivery attempt. Test records from GET /events/cancellations/ that predate event recording use "preview-<subscription_id>". |
account_id | integer | Chartsy account id. |
provider | string | Billing provider, e.g. stripe, paddle, polar. |
subscription_id | string | The provider's subscription id. |
customer_id | string | null | The provider's customer id. |
customer_name | string | null | Customer name, when the provider has one. |
customer_email | string | null | Customer email, when the provider has one. |
plan_name | string | null | Plan or price name. |
mrr_at_risk | number | null | Monthly amount the subscription contributes, normalized from yearly/weekly billing. |
currency | string | ISO 4217 code of mrr_at_risk. |
canceled_at | datetime | null | When the customer asked to cancel (ISO 8601, UTC). |
access_ends_at | datetime | null | When paid access ends. Later than canceled_at for cancel-at-period-end. |
cancellation_reason | string | null | The customer's reason, when the provider collects one. |
summary | string | Ready-to-post sentence describing the cancellation. |
mrr_at_risk, not churned MRR
Most cancellations keep paid access until the end of the billing period, so the revenue has not churned yet when the event fires. Chartsy counts it as churn on access_ends_at.
OpenAPI specification#
The full API is described in an OpenAPI 3.1 document you can load into Postman, Insomnia or Swagger UI: https://chartsy.app/openapi/chartsy-integrations-v1.json.
Questions or a client registration request: support@chartsy.app.


