API

Send a project's events, costs and payments to Sumly, and read its numbers back. The whole of it is one OpenAPI 3.1 file, for Postman, an SDK generator or an agent.

Keys

Each call names one project by its key: Authorization: Bearer <key>, at https://sumly.dev/api.

  • Site key. The project's public site key (sl_…), from its tracking line. Events only.
  • Secret key. The project's secret key (sk_sumly_…), made under Visitors › App. Server only.
  • Read key. The project's read key (rk_sumly_…), made by the owner under Connect an assistant. Reads only.

Money is in the currency's main unit (29.00, not 2900). Batches take up to 1000 items; each is checked on its own and the answer lists the refused ones by index. Up to 600 calls a minute.

Read a project's numbers

curl https://sumly.dev/api/v1/stats/overview \
  -H "Authorization: Bearer rk_sumly_…"

SDK

One package, no dependencies: npm i @sumly/sdk. @sumly/sdk/server is a typed client for everything below (Node, Bun, Deno, edge); @sumly/sdk/react-native is the app SDK; @sumly/sdk/web loads the site script in React or Next.js and gives typed track and identify.

import { Sumly } from '@sumly/sdk/server';

const sumly = new Sumly({ key: process.env.SUMLY_KEY });
await sumly.track('u_1842', 'trial_started', { plan: 'pro' });
const { mrr } = await sumly.stats.overview();

The app SDKs are also single files to copy: sumly.ts for React Native and Expo, Sumly.swift and Sumly.kt. The guide.

For AI assistants

ChatGPT, Claude and other MCP clients reach the same numbers, and more, at https://sumly.dev/mcp, signed in with OAuth: no key to copy. See llms.txt and skill.md.

Read

A project's numbers, the same answers the MCP tools give.

GET /api/v1/stats/overview Overview

MRR, ARR, paying customers, trials, churn, the last 30 days of money, costs and profit, and the next milestone. In euros.

Key: read key or secret key.

GET /api/v1/stats/visitors Visitors

Visitors, page views, bounce and visit length against the period before, by day, and the top sources, pages, places and devices.

Key: read key or secret key.

GET /api/v1/stats/sources Revenue by source

Net revenue in the range by the source of each buyer's first visit, with buyers and visitors per source. In euros.

Key: read key or secret key.

Events

Page views, screens and actions from a server or an app without the script.

POST /api/v1/events Send events

One event, or {"events": [...]} with up to 1000. page, screen and track count as visits; identify (a user's name and e-mail) and forget need the secret key and are never visits.

The app SDKs send here with the public key: sumly.ts for React Native and Expo, Sumly.swift and Sumly.kt. They send screens, and track app_installed once per install, with props.install_referrer on Android, which ties the install to the web visit behind it. A server sending app_installed for a phone passes the phone's address as ip; it is only hashed for the match, never kept.

Key: site key or secret key.

Fields
  • type* page | screen | track | identify | forget
  • user_id* string Your id for the user; RevenueCat's app user id ties their payments to them.
  • url string A page event's full URL.
  • title string
  • screen string A screen event's name.
  • name string A track event's name.
  • props object A track event's properties.
  • platform web | ios | android | mac | windows | watch | other
  • app_version string
  • country string Where the user is. From a server, send it: the request says where the server is.
  • referrer string
  • utm object
  • at date-time or ms When, up to a week back. Defaults to now.
  • ip string Only on app_installed from a server: the phone's address, hashed for the match with the web visit and never kept.
  • traits object For identify: an empty one clears it, one left out stays.
{
    "sdk": "react-native 1.2.0",
    "events": [
        {
            "type": "screen",
            "user_id": "u_1842",
            "screen": "Paywall",
            "platform": "ios",
            "country": "SI"
        },
        {
            "type": "track",
            "user_id": "u_1842",
            "name": "trial_started",
            "props": {
                "plan": "pro"
            }
        },
        {
            "type": "identify",
            "user_id": "u_1842",
            "traits": {
                "name": "Ana Novak",
                "email": "[email protected]"
            }
        }
    ]
}
POST /api/v1/active Who is using the app now

The first way Sumly took app users: each becomes a screen event on "App". /v1/events does the same and more.

Key: site key or secret key.

Fields
  • id* string
  • platform web | ios | android | mac | windows | watch | other
  • country string
POST /api/v1/bots Send crawler hits

Requests your server saw, by user agent: the bots and AI crawlers among them are counted under Visitors › Bots. Others are ignored. One hit or {"hits": [...]}.

Key: site key or secret key.

Fields
  • user_agent* string
  • path string
  • at string

Money

Costs and payments Sumly does not read from a provider.

POST /api/v1/costs Send costs

One cost, or {"costs": [...]}. Each has your own id: sending it again updates it, never counts it twice. A cost with a creator, campaign or channel counts against that on ROI.

Key: secret key.

Fields
  • id* string Your own id for it.
  • date* string
  • name* string
  • amount* number
  • currency string
  • category string
  • creator string
  • campaign string
  • channel string
{
    "costs": [
        {
            "id": "payout-2026-10-tiktok-ana",
            "date": "2026-10-31",
            "name": "TikTok creator payout",
            "amount": 250,
            "currency": "EUR",
            "creator": "@ana"
        }
    ]
}
DELETE /api/v1/costs/{id} Take back a cost

By the id you sent it with: a cancelled payout.

Key: secret key.

POST /api/v1/payments Send payments

Payments, refunds and cancelled subscriptions from a system Sumly does not read. They land in the same ledger as a provider's: MRR, revenue, customers and attribution count them. A payment with an interval starts or renews a subscription paid until the end of it; one neither renewed nor cancelled lapses three days later. Name the buyer with visitor_id (the sumly_vid cookie) or user_id to trace them to their first visit.

Key: secret key.

Fields
  • type payment | refund | cancel
  • id* string Your own id for it.
  • amount number
  • currency string
  • fee number
  • at string When; not in the future. Defaults to now.
  • interval day | week | month | year Left out for a one-off payment.
  • interval_count integer
  • customer_id string
  • email string Kept only as a hash.
  • customer_name string
  • subscription_id string
  • product string
  • plan string
  • description string
  • country string
  • visitor_id string The sumly_vid cookie's value, to trace the payment to its first visit.
  • user_id string The app user id sent with events.
  • payment_id string For a refund: the id of the payment it refunds.
  • ends_at string For a cancel: when access ends.
  • reason string
{
    "payments": [
        {
            "id": "inv_1042",
            "amount": 29,
            "currency": "EUR",
            "interval": "month",
            "customer_id": "cust_77",
            "product": "Pro",
            "visitor_id": "3f2c9a1e-7b44-4d0a-9e1f-2a6b8c0d5e11",
            "country": "SI"
        },
        {
            "type": "refund",
            "id": "ref_88",
            "payment_id": "inv_1001",
            "amount": 29
        }
    ]
}
DELETE /api/v1/payments/{id} Take back a payment

A payment or refund sent by mistake, by the id you sent it with.

Key: secret key.

Script

What the site script (s.js) sends. You do not call this yourself.

POST /api/e The script's collector

Where s.js sends page views and events, in short field names. Shown for completeness: use the script, or /v1/events from a server. The answer is always an empty 204, so nobody learns whether a key is real. No IP address is kept.

No key: the address is the secret.

Fields
  • s* string The site key.
  • v string Visitor id from the sumly_vid cookie.
  • t pageview | event | identify | checkout
  • u string The page's full URL.
  • r string The referrer.
  • n string An event's name.
  • p object An event's properties.
  • w integer Screen width.
  • c 0 | 1 Cookie consent, where it is asked.
  • ts integer When, in milliseconds.
  • batch array

Other

GET /api/v1/ping Check a key

Says which project a key belongs to and whether it is the secret key.

Key: site key or secret key.

POST /api/v1/marks/{token} Mark the charts

A release or deploy as a mark on the charts. Takes GitHub's and Vercel's webhooks as they are, or {"label": "…"} from any script. The address, made under Visitors › Insights › Marks, is the secret; no key is sent.

No key: the address is the secret.

Fields
  • label string
  • day string Defaults to today.
{
    "label": "v2.4: new paywall"
}
Pages, integrations, guides and answers