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 URLhttps://dashboard.chartsy.app/api/integrations/v1/
FormatJSON responses (UTF-8). POST bodies as JSON.
AuthenticationOAuth 2.0 authorization code with PKCE, bearer access tokens
Scopechartsy:read
VersioningThe version is in the path (v1). Fields may be added to v1; none are removed or renamed.
OpenAPIchartsy-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.

StepEndpoint
Authorize (browser)GET https://dashboard.chartsy.app/o/authorize/
Exchange code / refreshPOST https://dashboard.chartsy.app/o/token/
Revoke a tokenPOST 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=S256

The 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.

StatusBodyMeaning
200 / 201The resourceSuccess. 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/

The connection test. Returns the Chartsy login the token belongs to. Use email as the connection label.

Example response

{
  "id": 171,
  "email": "founder@example.com"
}

GET/accounts/

The login's active accounts, oldest first. Use it to fill an account dropdown; the id is the account_id other endpoints take.
FieldTypeDescription
idintegerAccount id.
namestringDisplay name.
currencystringDefault currency, ISO 4217.
payment_processorstring | nullConnected billing provider, e.g. Stripe.

Example response

[
  {
    "id": 4776,
    "name": "Acme (Stripe)",
    "currency": "USD",
    "payment_processor": "Stripe"
  }
]

GET/sites/

Websites tracked with the Chartsy tracking script. Use it to fill a site dropdown; the 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/

One item per settled day, the 30 most recent stored days, with id "<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

FieldTypeDescription
account_idinteger, optionalWhich 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/

The most recent settled day only, as a one-item array, with id "<account_id>-<date>". Computed live, with an optional site filter. Fields: Daily summary.

Parameters

FieldTypeDescription
account_idinteger, optionalWhich of your accounts to read (from GET /accounts/). Required only when the login has more than one account.
site_idinteger, optionalLimit 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/

Recent cancellations, the test/sample list for the Subscription Canceled instant trigger. Items have exactly the shape that is delivered to hooks. If fewer events have been recorded than 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

FieldTypeDescription
account_idinteger, optionalWhich of your accounts to read (from GET /accounts/). Required only when the login has more than one account.
limitinteger, optionalItems 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/

The 25 most recently achieved goals set in Chartsy. id is the goal id.
FieldTypeDescription
idintegerGoal id.
titlestringGoal title, or a generated one.
goal_typestringmrr, arr, total_subscribers or churn_rate.
goal_type_labelstringReadable goal type.
target_valuenumberTarget.
baseline_valuenumber | nullValue when the goal was set.
deadlinedate | nullGoal deadline.
achieved_atdateWhen the goal was reached.
account_id / account_name / currencyThe account it belongs to.
summarystringReady-to-post sentence.

Parameters

FieldTypeDescription
account_idinteger, optionalWhich 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/

Every MRR milestone at or below current MRR, highest first: 100, 250, 500, 1k, 2.5k, 5k, 7.5k, 10k, 15k, 20k, 25k, 30k, 40k, 50k, 75k, 100k, 150k, 200k, 250k, 500k, 750k and 1M, in the account currency. The id is "<account_id>-mrr-<milestone>", so each milestone is new exactly once, the first time MRR passes it.

Parameters

FieldTypeDescription
account_idinteger, optionalWhich 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/

Customers at risk of churning: subscriptions that are paused (id paused-<subscription_id>) and customers with 2 or more failed payments this quarter (id failed-<customer_id>-<quarter>).
FieldTypeDescription
idstringStable id, see above.
reasonstringpaused or failed_payments.
customer_idstringThe provider's customer id.
subscription_idstring | nullSet for paused subscriptions.
failed_payments_this_quarterinteger | nullSet for failed_payments.
paused_datedate | nullSet for paused.
days_pausedinteger | nullSet for paused.
account_id / account_nameThe account it belongs to.
summarystringReady-to-post sentence.

Parameters

FieldTypeDescription
account_idinteger, optionalWhich 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/

Subscribe. Send a JSON body. Returns 201 with the hook; store its 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

FieldTypeDescription
eventstring, requiredsubscription_canceled
target_urlstring, requiredThe callback URL. https on port 443, resolving to a public address. At most 2000 characters.
account_idinteger, optionalWhich 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}/

Unsubscribe. Deliveries stop at once, including pending retries. Repeating the call is harmless. Returns the hook with 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}/

One hook belonging to the caller.

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/

All active hooks belonging to the caller.

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/

Current MRR and ARR (MRR × 12).

Parameters

FieldTypeDescription
account_idinteger, optionalWhich 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/

One day's figures for any date. Fields: Daily summary.

Parameters

FieldTypeDescription
account_idinteger, optionalWhich of your accounts to read (from GET /accounts/). Required only when the login has more than one account.
datedate, optionalDay to summarize, YYYY-MM-DD. Defaults to yesterday.
site_idinteger, optionalLimit 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/

The headline numbers of a business review: current MRR, this month's new subscribers and churn rate, the top 3 customers' share of MRR, all-time refunds, at-risk counts, MRR and paying customers by plan per month over the window (the last 50 rows) and the top 10 traffic sources in the window.

Parameters

FieldTypeDescription
account_idinteger, optionalWhich of your accounts to read (from GET /accounts/). Required only when the login has more than one account.
monthsinteger, optionalLook-back window, 1-36. Default 12.
site_idinteger, optionalLimit 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/

Which marketing sources brought signups that became paying customers, what those customers pay today and how many churned, across all your tracked sites. Signups in the window are grouped by the visit credited with them (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

FieldTypeDescription
start_datedate, optionalFirst day included, YYYY-MM-DD. Defaults to 30 days ago.
end_datedate, optionalLast day included, YYYY-MM-DD. Defaults to today.
bystring, optionalsource (default), medium, campaign or content.
touchstring, optionallast (default) or first: which visit gets the credit.
site_idinteger, optionalLimit 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/

Website visits and signups grouped by a traffic dimension, from Chartsy website tracking.

Parameters

FieldTypeDescription
start_datedate, optionalFirst day included, YYYY-MM-DD. Defaults to 30 days ago.
end_datedate, optionalLast day included, YYYY-MM-DD. Defaults to today.
bystring, optionalsource (default), medium, campaign, content, country, referrer or landing_path.
site_idinteger, optionalLimit 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/.

FieldTypeDescription
idstringStable id: "<account_id>-<date>" on /events/daily-summaries/, "<account_id>:<date>" on /events/daily-snapshots/.
datedateThe day the figures cover (UTC).
account_idintegerChartsy account id.
account_namestringAccount display name.
currencystringISO 4217 code of every money field.
mrrnumberMonthly recurring revenue at the end of the day.
revenuenumberRevenue collected that day.
revenue_previous_daynumberRevenue the day before.
revenue_trailing_daily_averagenumberAverage daily revenue over the previous 7 days.
revenue_change_vs_previous_day_percentnumber | nullChange vs the previous day. Null when there is nothing to compare against.
revenue_change_vs_trailing_average_percentnumber | nullChange vs the 7-day average.
new_subscribersintegerNew paying subscribers that day.
new_subscribers_previous_dayintegerNew paying subscribers the day before.
churned_customersintegerCustomers whose paid access ended that day.
churn_rate_percentnumber | nullCustomer churn rate for the day.
refund_countintegerRefunds issued that day.
total_refundednumberAmount refunded that day.
visitsinteger | nullWebsite visits. Null when website tracking is not installed.
unique_visitorsinteger | nullUnique visitors.
signupsinteger | nullTracked signups.
visits_change_vs_trailing_average_percentnumber | nullVisits change vs the 7-day average.
top_sourcestring | nullTraffic source with the most visits.
warning_countintegerNumber of data-quality warnings about these figures.
warningsstringThe warnings, one per line. Empty when there are none.
summarystringReady-to-post multi-line text summary.
revenue_by_planarrayLine items: { plan, revenue }.
traffic_by_sourcearrayUp to 10 line items: { source, visits, signups }.

Cancellation

Delivered to subscription_canceled hooks and returned by /events/cancellations/.

FieldTypeDescription
idinteger | stringEvent id; the same on every delivery attempt. Test records from GET /events/cancellations/ that predate event recording use "preview-<subscription_id>".
account_idintegerChartsy account id.
providerstringBilling provider, e.g. stripe, paddle, polar.
subscription_idstringThe provider's subscription id.
customer_idstring | nullThe provider's customer id.
customer_namestring | nullCustomer name, when the provider has one.
customer_emailstring | nullCustomer email, when the provider has one.
plan_namestring | nullPlan or price name.
mrr_at_risknumber | nullMonthly amount the subscription contributes, normalized from yearly/weekly billing.
currencystringISO 4217 code of mrr_at_risk.
canceled_atdatetime | nullWhen the customer asked to cancel (ISO 8601, UTC).
access_ends_atdatetime | nullWhen paid access ends. Later than canceled_at for cancel-at-period-end.
cancellation_reasonstring | nullThe customer's reason, when the provider collects one.
summarystringReady-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.

Related articles

Chartsy Team

Written by

Chartsy Team

Analytics team at Chartsy

The Chartsy Team writes guides, product updates, and resources to help SaaS and eCommerce founders make sense of their metrics, without SQL or spreadsheets.

Chartsy
Ministry of Economy and Innovation
Startup Albania

The Chartsy program is realized with the financial support of the Albanian Government through the Ministry of Economy and Innovation, under the Grant 2026 scheme, and is implemented by the Innovation4Albania Agency.