{
  "openapi": "3.1.0",
  "info": {
    "title": "Sumly API",
    "version": "1.0.0",
    "summary": "Send a project's events, costs and payments to Sumly, and read its numbers back.",
    "description": "Sumly shows a founder's revenue (MRR, payments, customers) beside the visitors who brought it. This API is for what Sumly cannot read itself: events from your server, costs, payments from a system it does not connect to, and reading a project's numbers into a script or another dashboard.\n\nEvery call names one project by its key, sent as `Authorization: Bearer <key>`:\n\n- **Site key** (`sl_…`, public): the one in the site script. Sends events only.\n- **Secret key** (`sk_sumly_…`): made in Sumly under Visitors › App. Keep it on a server. Sends events, `identify` and `forget`, costs and payments, and reads.\n- **Read key** (`rk_sumly_…`): made by the owner under Connect an assistant. Reads only.\n\nMoney is in the currency's main unit (29.00, not 2900). Batches take up to 1000 items; each item is checked on its own and the answer lists the refused ones by index. The limit is 600 calls a minute per address. The demo's keys, shown on its public pages, answer 401.\n\nAI assistants (ChatGPT, Claude) connect to the same numbers over MCP at https://sumly.dev/mcp, signed in with OAuth.",
    "contact": {
      "name": "Sumly support",
      "url": "https://sumly.dev/support"
    },
    "termsOfService": "https://sumly.dev/terms"
  },
  "servers": [
    {
      "url": "https://sumly.dev/api"
    }
  ],
  "tags": [
    {
      "name": "Read",
      "description": "A project's numbers, the same answers the MCP tools give."
    },
    {
      "name": "Events",
      "description": "Page views, screens and actions from a server or an app without the script."
    },
    {
      "name": "Money",
      "description": "Costs and payments Sumly does not read from a provider."
    },
    {
      "name": "Script",
      "description": "What the site script (s.js) sends. You do not call this yourself."
    },
    {
      "name": "Other"
    }
  ],
  "paths": {
    "/v1/ping": {
      "get": {
        "tags": [
          "Other"
        ],
        "operationId": "ping",
        "summary": "Check a key",
        "description": "Says which project a key belongs to and whether it is the secret key.",
        "security": [
          {
            "siteKey": []
          },
          {
            "secretKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "The key works.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "project": {
                      "type": "string"
                    },
                    "key": {
                      "type": "string",
                      "enum": [
                        "secret",
                        "public"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnknownKey"
          },
          "402": {
            "$ref": "#/components/responses/NoPlan"
          }
        }
      }
    },
    "/v1/stats/overview": {
      "get": {
        "tags": [
          "Read"
        ],
        "operationId": "statsOverview",
        "summary": "Overview",
        "description": "MRR, ARR, paying customers, trials, churn, the last 30 days of money, costs and profit, and the next milestone. In euros.",
        "security": [
          {
            "readKey": []
          },
          {
            "secretKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "The overview.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Overview"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnknownKey"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          }
        }
      }
    },
    "/v1/stats/visitors": {
      "get": {
        "tags": [
          "Read"
        ],
        "operationId": "statsVisitors",
        "summary": "Visitors",
        "description": "Visitors, page views, bounce and visit length against the period before, by day, and the top sources, pages, places and devices.",
        "security": [
          {
            "readKey": []
          },
          {
            "secretKey": []
          }
        ],
        "parameters": [
          {
            "name": "range",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "24h",
                "7d",
                "30d",
                "90d"
              ],
              "default": "30d"
            },
            "description": "How far back from now."
          },
          {
            "name": "filter",
            "in": "query",
            "required": false,
            "style": "deepObject",
            "explode": true,
            "schema": {
              "type": "object",
              "properties": {
                "platform": {
                  "type": "string"
                },
                "app_version": {
                  "type": "string"
                },
                "source": {
                  "type": "string"
                },
                "referrer_host": {
                  "type": "string"
                },
                "utm_campaign": {
                  "type": "string"
                },
                "path": {
                  "type": "string"
                },
                "country": {
                  "type": "string"
                },
                "city": {
                  "type": "string"
                },
                "device": {
                  "type": "string"
                },
                "browser": {
                  "type": "string"
                },
                "os": {
                  "type": "string"
                }
              },
              "additionalProperties": false
            },
            "description": "Only visits matching these, as on the Visitors page: `filter[source]=Google`, `filter[country]=SI`."
          }
        ],
        "responses": {
          "200": {
            "description": "The visitors.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Visitors"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnknownKey"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          }
        }
      }
    },
    "/v1/stats/sources": {
      "get": {
        "tags": [
          "Read"
        ],
        "operationId": "statsSources",
        "summary": "Revenue by source",
        "description": "Net revenue in the range by the source of each buyer's first visit, with buyers and visitors per source. In euros.",
        "security": [
          {
            "readKey": []
          },
          {
            "secretKey": []
          }
        ],
        "parameters": [
          {
            "name": "range",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "24h",
                "7d",
                "30d",
                "90d"
              ],
              "default": "30d"
            },
            "description": "How far back from now."
          }
        ],
        "responses": {
          "200": {
            "description": "Revenue by source.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Sources"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnknownKey"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          }
        }
      }
    },
    "/v1/events": {
      "post": {
        "tags": [
          "Events"
        ],
        "operationId": "sendEvents",
        "summary": "Send events",
        "description": "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.\n\nThe app SDKs send here with the public key: [sumly.ts](https://sumly.dev/sdk/sumly.ts) for React Native and Expo, [Sumly.swift](https://sumly.dev/sdk/Sumly.swift) and [Sumly.kt](https://sumly.dev/sdk/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.",
        "security": [
          {
            "siteKey": []
          },
          {
            "secretKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/Event"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "events": {
                        "type": "array",
                        "items": {
                          "$ref": "#/components/schemas/Event"
                        },
                        "maxItems": 1000
                      },
                      "sdk": {
                        "type": "string",
                        "maxLength": 40,
                        "description": "The app SDK and its version (\"react-native 1.2.0\", \"swift 1.1.0\"). Its calls show as the SDK's on the Sources list."
                      }
                    },
                    "required": [
                      "events"
                    ]
                  }
                ]
              },
              "example": {
                "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": "ana@example.com"
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What was kept and what was refused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Kept"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnknownKey"
          },
          "402": {
            "$ref": "#/components/responses/NoPlan"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          }
        }
      }
    },
    "/v1/active": {
      "post": {
        "tags": [
          "Events"
        ],
        "operationId": "sendActive",
        "summary": "Who is using the app now",
        "description": "The first way Sumly took app users: each becomes a `screen` event on \"App\". `/v1/events` does the same and more.",
        "security": [
          {
            "siteKey": []
          },
          {
            "secretKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/ActiveUser"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "users": {
                        "type": "array",
                        "items": {
                          "$ref": "#/components/schemas/ActiveUser"
                        },
                        "maxItems": 1000
                      }
                    },
                    "required": [
                      "users"
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What was kept and what was refused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Kept"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnknownKey"
          },
          "402": {
            "$ref": "#/components/responses/NoPlan"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          }
        }
      }
    },
    "/v1/costs": {
      "post": {
        "tags": [
          "Money"
        ],
        "operationId": "sendCosts",
        "summary": "Send costs",
        "description": "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.",
        "security": [
          {
            "secretKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/Cost"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "costs": {
                        "type": "array",
                        "items": {
                          "$ref": "#/components/schemas/Cost"
                        },
                        "maxItems": 1000
                      }
                    },
                    "required": [
                      "costs"
                    ]
                  }
                ]
              },
              "example": {
                "costs": [
                  {
                    "id": "payout-2026-10-tiktok-ana",
                    "date": "2026-10-31",
                    "name": "TikTok creator payout",
                    "amount": 250,
                    "currency": "EUR",
                    "creator": "@ana"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Added, updated and unchanged.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "added": {
                      "type": "integer"
                    },
                    "updated": {
                      "type": "integer"
                    },
                    "unchanged": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/NeedsSecret"
          },
          "401": {
            "$ref": "#/components/responses/UnknownKey"
          },
          "402": {
            "$ref": "#/components/responses/NoPlan"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          }
        }
      }
    },
    "/v1/costs/{id}": {
      "delete": {
        "tags": [
          "Money"
        ],
        "operationId": "deleteCost",
        "summary": "Take back a cost",
        "description": "By the `id` you sent it with: a cancelled payout.",
        "security": [
          {
            "secretKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 191
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Taken back.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deleted"
                }
              }
            }
          },
          "404": {
            "description": "No cost with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deleted"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnknownKey"
          },
          "402": {
            "$ref": "#/components/responses/NoPlan"
          },
          "403": {
            "$ref": "#/components/responses/NeedsSecret"
          }
        }
      }
    },
    "/v1/payments": {
      "post": {
        "tags": [
          "Money"
        ],
        "operationId": "sendPayments",
        "summary": "Send payments",
        "description": "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.",
        "security": [
          {
            "secretKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/Payment"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "payments": {
                        "type": "array",
                        "items": {
                          "$ref": "#/components/schemas/Payment"
                        },
                        "maxItems": 1000
                      }
                    },
                    "required": [
                      "payments"
                    ]
                  }
                ]
              },
              "example": {
                "payments": [
                  {
                    "id": "inv_1042",
                    "amount": 29.0,
                    "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.0
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Added, updated, unchanged and refused.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "added": {
                      "type": "integer"
                    },
                    "updated": {
                      "type": "integer"
                    },
                    "unchanged": {
                      "type": "integer"
                    },
                    "refused": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Refused"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/NeedsSecret"
          },
          "401": {
            "$ref": "#/components/responses/UnknownKey"
          },
          "402": {
            "$ref": "#/components/responses/NoPlan"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          },
          "429": {
            "$ref": "#/components/responses/TooMany"
          }
        }
      }
    },
    "/v1/payments/{id}": {
      "delete": {
        "tags": [
          "Money"
        ],
        "operationId": "deletePayment",
        "summary": "Take back a payment",
        "description": "A payment or refund sent by mistake, by the `id` you sent it with.",
        "security": [
          {
            "secretKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 180
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Taken back.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deleted"
                }
              }
            }
          },
          "404": {
            "description": "No payment with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deleted"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnknownKey"
          },
          "402": {
            "$ref": "#/components/responses/NoPlan"
          },
          "403": {
            "$ref": "#/components/responses/NeedsSecret"
          }
        }
      }
    },
    "/v1/bots": {
      "post": {
        "tags": [
          "Events"
        ],
        "operationId": "sendBots",
        "summary": "Send crawler hits",
        "description": "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\": [...]}`.",
        "security": [
          {
            "siteKey": []
          },
          {
            "secretKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/BotHit"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "hits": {
                        "type": "array",
                        "items": {
                          "$ref": "#/components/schemas/BotHit"
                        }
                      }
                    },
                    "required": [
                      "hits"
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "How many were bots.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "kept": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnknownKey"
          }
        }
      }
    },
    "/v1/marks/{token}": {
      "post": {
        "tags": [
          "Other"
        ],
        "operationId": "addMark",
        "summary": "Mark the charts",
        "description": "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.",
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 32
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "day": {
                    "type": "string",
                    "format": "date",
                    "description": "Defaults to today."
                  }
                }
              },
              "example": {
                "label": "v2.4: new paywall"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The mark, or nothing to mark.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "mark": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "day": {
                      "type": "string",
                      "format": "date"
                    },
                    "note": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/e": {
      "post": {
        "tags": [
          "Script"
        ],
        "operationId": "collect",
        "summary": "The script's collector",
        "description": "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.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "s": {
                    "type": "string",
                    "description": "The site key."
                  },
                  "v": {
                    "type": "string",
                    "description": "Visitor id from the sumly_vid cookie."
                  },
                  "t": {
                    "type": "string",
                    "enum": [
                      "pageview",
                      "event",
                      "identify",
                      "checkout"
                    ],
                    "default": "pageview"
                  },
                  "u": {
                    "type": "string",
                    "description": "The page's full URL."
                  },
                  "r": {
                    "type": "string",
                    "description": "The referrer."
                  },
                  "n": {
                    "type": "string",
                    "description": "An event's name."
                  },
                  "p": {
                    "type": "object",
                    "properties": {},
                    "description": "An event's properties.",
                    "additionalProperties": true
                  },
                  "w": {
                    "type": "integer",
                    "description": "Screen width."
                  },
                  "c": {
                    "type": "integer",
                    "enum": [
                      0,
                      1
                    ],
                    "description": "Cookie consent, where it is asked."
                  },
                  "ts": {
                    "type": "integer",
                    "description": "When, in milliseconds."
                  },
                  "batch": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {},
                      "additionalProperties": true
                    },
                    "maxItems": 50
                  }
                },
                "required": [
                  "s"
                ]
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Always."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "siteKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "The project's public site key (sl_…), from its tracking line. Events only."
      },
      "secretKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "The project's secret key (sk_sumly_…), made under Visitors › App. Server only."
      },
      "readKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "The project's read key (rk_sumly_…), made by the owner under Connect an assistant. Reads only."
      }
    },
    "responses": {
      "UnknownKey": {
        "description": "No project has this key, or it is the demo's.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NoPlan": {
        "description": "The workspace's trial ended without a plan: nothing is kept until it has one.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NeedsSecret": {
        "description": "Costs and payments need the secret key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Invalid": {
        "description": "The batch itself is wrong (not a list, more than 1000).",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "message": {
                  "type": "string"
                },
                "errors": {
                  "type": "object",
                  "properties": {},
                  "additionalProperties": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "TooMany": {
        "description": "More than 600 calls a minute."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "ok": {
            "const": false
          },
          "error": {
            "type": "string"
          }
        },
        "required": [
          "ok",
          "error"
        ]
      },
      "Refused": {
        "type": "object",
        "properties": {
          "index": {
            "type": "integer"
          },
          "reason": {
            "type": "string"
          }
        }
      },
      "Kept": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "kept": {
            "type": "integer"
          },
          "refused": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Refused"
            },
            "maxItems": 20
          }
        }
      },
      "Deleted": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "deleted": {
            "type": "integer"
          }
        }
      },
      "Event": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "page",
              "screen",
              "track",
              "identify",
              "forget"
            ]
          },
          "user_id": {
            "type": "string",
            "pattern": "^(?:[A-Za-z0-9_.:-]{1,55}|\\$RCAnonymousID:[A-Za-z0-9]{1,40})$",
            "description": "Your id for the user; RevenueCat's app user id ties their payments to them."
          },
          "url": {
            "type": "string",
            "description": "A page event's full URL."
          },
          "title": {
            "type": "string",
            "maxLength": 200
          },
          "screen": {
            "type": "string",
            "description": "A screen event's name."
          },
          "name": {
            "type": "string",
            "maxLength": 80,
            "description": "A track event's name."
          },
          "props": {
            "type": "object",
            "properties": {},
            "additionalProperties": true,
            "description": "A track event's properties."
          },
          "platform": {
            "type": "string",
            "enum": [
              "web",
              "ios",
              "android",
              "mac",
              "windows",
              "watch",
              "other"
            ]
          },
          "app_version": {
            "type": "string",
            "maxLength": 30
          },
          "country": {
            "type": "string",
            "pattern": "^[A-Za-z]{2}$",
            "description": "Where the user is. From a server, send it: the request says where the server is."
          },
          "referrer": {
            "type": "string"
          },
          "utm": {
            "type": "object",
            "properties": {
              "source": {
                "type": "string",
                "maxLength": 80
              },
              "medium": {
                "type": "string",
                "maxLength": 80
              },
              "campaign": {
                "type": "string",
                "maxLength": 80
              },
              "term": {
                "type": "string",
                "maxLength": 80
              },
              "content": {
                "type": "string",
                "maxLength": 80
              }
            }
          },
          "at": {
            "oneOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "integer",
                "description": "Milliseconds since 1970."
              }
            ],
            "description": "When, up to a week back. Defaults to now."
          },
          "ip": {
            "type": "string",
            "description": "Only on app_installed from a server: the phone's address, hashed for the match with the web visit and never kept."
          },
          "traits": {
            "type": "object",
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "email"
              }
            },
            "description": "For identify: an empty one clears it, one left out stays."
          }
        },
        "required": [
          "type",
          "user_id"
        ]
      },
      "ActiveUser": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "platform": {
            "type": "string",
            "enum": [
              "web",
              "ios",
              "android",
              "mac",
              "windows",
              "watch",
              "other"
            ]
          },
          "country": {
            "type": "string",
            "pattern": "^[A-Za-z]{2}$"
          }
        },
        "required": [
          "id"
        ]
      },
      "Cost": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "maxLength": 191,
            "description": "Your own id for it."
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "name": {
            "type": "string",
            "maxLength": 120
          },
          "amount": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 1000000
          },
          "currency": {
            "type": "string",
            "minLength": 3,
            "maxLength": 3,
            "default": "EUR"
          },
          "category": {
            "type": "string",
            "maxLength": 40
          },
          "creator": {
            "type": "string",
            "maxLength": 120
          },
          "campaign": {
            "type": "string",
            "maxLength": 120
          },
          "channel": {
            "type": "string",
            "maxLength": 120
          }
        },
        "required": [
          "id",
          "date",
          "name",
          "amount"
        ]
      },
      "Payment": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "payment",
              "refund",
              "cancel"
            ],
            "default": "payment"
          },
          "id": {
            "type": "string",
            "maxLength": 180,
            "description": "Your own id for it."
          },
          "amount": {
            "type": "number",
            "exclusiveMinimum": 0
          },
          "currency": {
            "type": "string",
            "minLength": 3,
            "maxLength": 3,
            "default": "EUR"
          },
          "fee": {
            "type": "number",
            "minimum": 0
          },
          "at": {
            "type": "string",
            "format": "date-time",
            "description": "When; not in the future. Defaults to now."
          },
          "interval": {
            "type": "string",
            "enum": [
              "day",
              "week",
              "month",
              "year"
            ],
            "description": "Left out for a one-off payment."
          },
          "interval_count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 36,
            "default": 1
          },
          "customer_id": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Kept only as a hash."
          },
          "customer_name": {
            "type": "string"
          },
          "subscription_id": {
            "type": "string",
            "maxLength": 150
          },
          "product": {
            "type": "string",
            "maxLength": 120
          },
          "plan": {
            "type": "string",
            "maxLength": 120
          },
          "description": {
            "type": "string"
          },
          "country": {
            "type": "string",
            "pattern": "^[A-Za-z]{2}$"
          },
          "visitor_id": {
            "type": "string",
            "description": "The sumly_vid cookie's value, to trace the payment to its first visit."
          },
          "user_id": {
            "type": "string",
            "description": "The app user id sent with events."
          },
          "payment_id": {
            "type": "string",
            "description": "For a refund: the id of the payment it refunds."
          },
          "ends_at": {
            "type": "string",
            "format": "date-time",
            "description": "For a cancel: when access ends."
          },
          "reason": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "BotHit": {
        "type": "object",
        "properties": {
          "user_agent": {
            "type": "string"
          },
          "path": {
            "type": "string"
          },
          "at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "user_agent"
        ]
      },
      "Overview": {
        "type": "object",
        "properties": {
          "scope": {
            "type": "string",
            "description": "The project."
          },
          "currency": {
            "const": "EUR"
          },
          "as_of": {
            "type": "string"
          },
          "mrr": {
            "type": "number"
          },
          "mrr_30_days_ago": {
            "type": "number"
          },
          "arr": {
            "type": "number"
          },
          "paying_customers": {
            "type": "integer"
          },
          "paying_subscriptions": {
            "type": "integer"
          },
          "revenue_per_customer": {
            "type": [
              "number",
              "null"
            ]
          },
          "trials_running": {
            "type": "integer"
          },
          "set_to_cancel_mrr": {
            "type": "number"
          },
          "new_mrr_30_days": {
            "type": "number"
          },
          "lost_mrr_30_days": {
            "type": "number"
          },
          "revenue_churn_percent_30_days": {
            "type": [
              "number",
              "null"
            ]
          },
          "last_30_days": {
            "type": "object",
            "properties": {
              "gross": {
                "type": "number"
              },
              "refunds": {
                "type": "number"
              },
              "fees": {
                "type": "number"
              },
              "net": {
                "type": "number"
              },
              "sales": {
                "type": "integer"
              },
              "people_who_paid": {
                "type": "integer"
              }
            }
          },
          "net_revenue_previous_30_days": {
            "type": "number"
          },
          "net_revenue_month_to_date": {
            "type": "number"
          },
          "net_revenue_last_month": {
            "type": "number"
          },
          "costs_30_days": {
            "type": "number"
          },
          "profit_30_days": {
            "type": "number"
          },
          "monthly_burn": {
            "type": "number"
          },
          "next_mrr_milestone": {
            "type": [
              "object",
              "null"
            ]
          },
          "visitors_30_days": {
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "additionalProperties": true
      },
      "Headline": {
        "type": "object",
        "properties": {
          "visitors": {
            "type": "integer"
          },
          "pageviews": {
            "type": "integer"
          },
          "pages_per_visit": {
            "type": [
              "number",
              "null"
            ]
          },
          "left_after_one_page_percent": {
            "type": [
              "number",
              "null"
            ]
          },
          "visit_seconds": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "Row": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "visitors": {
            "type": "integer"
          },
          "views": {
            "type": "integer"
          }
        }
      },
      "Visitors": {
        "type": "object",
        "properties": {
          "scope": {
            "type": "string"
          },
          "range": {
            "type": "string"
          },
          "only": {
            "type": [
              "object",
              "null"
            ]
          },
          "now": {
            "$ref": "#/components/schemas/Headline"
          },
          "before": {
            "$ref": "#/components/schemas/Headline"
          },
          "daily": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "day": {
                  "type": "string",
                  "format": "date"
                },
                "visitors": {
                  "type": "integer"
                },
                "views": {
                  "type": "integer"
                }
              }
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Row"
            }
          },
          "referrers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Row"
            }
          },
          "campaigns": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Row"
            }
          },
          "pages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Row"
            }
          },
          "entry_pages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Row"
            }
          },
          "exit_pages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Row"
            }
          },
          "countries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Row"
            }
          },
          "cities": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Row"
            }
          },
          "platforms": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Row"
            }
          },
          "devices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Row"
            }
          },
          "browsers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Row"
            }
          },
          "systems": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Row"
            }
          }
        },
        "additionalProperties": true
      },
      "Sources": {
        "type": "object",
        "properties": {
          "scope": {
            "type": "string"
          },
          "range": {
            "type": "string"
          },
          "currency": {
            "const": "EUR"
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "source": {
                  "type": "string"
                },
                "net": {
                  "type": "number"
                },
                "buyers": {
                  "type": "integer"
                },
                "visitors": {
                  "type": "integer"
                },
                "buyers_per_visitor_percent": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "net_per_visitor": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              }
            }
          },
          "traced_to_a_source": {
            "type": "number"
          },
          "not_traced": {
            "type": "number"
          }
        }
      }
    }
  }
}
