{"openapi":"3.1.0","info":{"title":"StableAnalytics","description":"Prepaid stripped-down web analytics for page visits, sources, UTMs, browsers, operating systems, devices, and recent visits.","version":"0.1.0","x-guidance":"# StableAnalytics API\n\nStableAnalytics is a prepaid web analytics API.\n\nAuth model:\n- Pay with a wallet to create or top up a site (x402 or MPP).\n- Owner queries are authenticated with Sign-In-With-X (SIWX): the same\n  wallet that paid signs a message to prove identity. No per-call payment,\n  no bearer tokens, no delegation — if you hold the key, you can read.\n- The public tracker script hits /api/ingest with the site's public token\n  (no signature required).\n\nRecommended flow:\n1. Create a site: POST /api/site/create with x402 or MPP. The paying wallet\n   becomes the owner. Required: name, sku. Optional: domain.\n   - If you already know the hostname you'll track, pass it as `domain`.\n   - If you don't know the hostname yet (e.g. you're about to deploy to\n     StableUpload, Vercel, or any host that assigns the URL after deploy),\n     omit `domain` and set it later with POST /api/site/domain.\n2. If domain was omitted at create, set it before installing the embed:\n   POST /api/site/domain with body { site_id, domain }. Until the site\n   has a domain, /api/ingest returns 403 \"Domain not configured\".\n3. Install the returned embed snippet on your site.\n4. List and associate sites from the same wallet with SIWX:\n   - GET /api/sites returns each site's id, name, domain (nullable),\n     status, usage, created_at, last_active_at, and visits_total. Use\n     this to pick the site you want to work with.\n5. Query analytics with SIWX from the same wallet:\n   - GET /api/site?id=...\n   - GET /api/site/overview?site_id=...&period=24h|7d|30d\n   - GET /api/site/pages?site_id=...\n   - GET /api/site/sources?site_id=...\n   - GET /api/site/tech?site_id=...\n   - GET /api/site/visits?site_id=...\n6. Top up when the balance runs low: POST /api/site/topup.\n\nTracked fields:\n- pathname\n- referrer\n- utm_source / utm_medium / utm_campaign / utm_term / utm_content\n- browser / os / device\n- country\n- recent visit metadata\n\nPricing model:\n- prepaid tracked-request caps (100k, 1M, 10M)\n- no month-end billing\n- same feature set on every cap\n","guidance":"# StableAnalytics API\n\nStableAnalytics is a prepaid web analytics API.\n\nAuth model:\n- Pay with a wallet to create or top up a site (x402 or MPP).\n- Owner queries are authenticated with Sign-In-With-X (SIWX): the same\n  wallet that paid signs a message to prove identity. No per-call payment,\n  no bearer tokens, no delegation — if you hold the key, you can read.\n- The public tracker script hits /api/ingest with the site's public token\n  (no signature required).\n\nRecommended flow:\n1. Create a site: POST /api/site/create with x402 or MPP. The paying wallet\n   becomes the owner. Required: name, sku. Optional: domain.\n   - If you already know the hostname you'll track, pass it as `domain`.\n   - If you don't know the hostname yet (e.g. you're about to deploy to\n     StableUpload, Vercel, or any host that assigns the URL after deploy),\n     omit `domain` and set it later with POST /api/site/domain.\n2. If domain was omitted at create, set it before installing the embed:\n   POST /api/site/domain with body { site_id, domain }. Until the site\n   has a domain, /api/ingest returns 403 \"Domain not configured\".\n3. Install the returned embed snippet on your site.\n4. List and associate sites from the same wallet with SIWX:\n   - GET /api/sites returns each site's id, name, domain (nullable),\n     status, usage, created_at, last_active_at, and visits_total. Use\n     this to pick the site you want to work with.\n5. Query analytics with SIWX from the same wallet:\n   - GET /api/site?id=...\n   - GET /api/site/overview?site_id=...&period=24h|7d|30d\n   - GET /api/site/pages?site_id=...\n   - GET /api/site/sources?site_id=...\n   - GET /api/site/tech?site_id=...\n   - GET /api/site/visits?site_id=...\n6. Top up when the balance runs low: POST /api/site/topup.\n\nTracked fields:\n- pathname\n- referrer\n- utm_source / utm_medium / utm_campaign / utm_term / utm_content\n- browser / os / device\n- country\n- recent visit metadata\n\nPricing model:\n- prepaid tracked-request caps (100k, 1M, 10M)\n- no month-end billing\n- same feature set on every cap\n"},"servers":[{"url":"https://stableanalytics.dev"}],"tags":[{"name":"Site"},{"name":"Sites"}],"paths":{"/api/site/create":{"post":{"operationId":"site_create","summary":"Create a tracked site and buy its initial prepaid request cap.","tags":["Site"],"x-payment-info":{"price":{"mode":"dynamic","currency":"USD","min":"0.10","max":"2.00"},"protocols":[{"x402":{}},{"mpp":{"method":"tempo","intent":"charge","currency":"0x20c000000000000000000000b9537d11c60e8b50"}}]},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"domain":{"type":"string","minLength":1,"maxLength":255},"sku":{"type":"string","enum":["100k","1m","10m"]}},"required":["name","sku"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"site":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"domain":{"anyOf":[{"type":"string"},{"type":"null"}]},"status":{"type":"string","enum":["active","archived","exhausted"]},"public_token":{"type":"string"},"created_at":{"type":"string"}},"required":["id","name","domain","status","public_token","created_at"],"additionalProperties":false},"usage":{"type":"object","properties":{"cap":{"type":"integer","minimum":0,"maximum":9007199254740991},"used":{"type":"integer","minimum":0,"maximum":9007199254740991},"remaining":{"type":"integer","minimum":0,"maximum":9007199254740991},"percent_used":{"type":"number","minimum":0},"retention_days":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["cap","used","remaining","percent_used","retention_days"],"additionalProperties":false},"embed":{"type":"object","properties":{"script_src":{"type":"string"},"snippet":{"type":"string"}},"required":["script_src","snippet"],"additionalProperties":false}},"required":["site","usage","embed"],"additionalProperties":false}}}},"402":{"description":"Payment Required"}}}},"/api/site/topup":{"post":{"operationId":"site_topup","summary":"Top up an existing site with an additional prepaid request cap.","tags":["Site"],"x-payment-info":{"price":{"mode":"dynamic","currency":"USD","min":"0.10","max":"2.00"},"protocols":[{"x402":{}},{"mpp":{"method":"tempo","intent":"charge","currency":"0x20c000000000000000000000b9537d11c60e8b50"}}]},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"site_id":{"type":"string","minLength":1},"sku":{"type":"string","enum":["100k","1m","10m"]}},"required":["site_id","sku"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"site_id":{"type":"string"},"added":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"usage":{"type":"object","properties":{"cap":{"type":"integer","minimum":0,"maximum":9007199254740991},"used":{"type":"integer","minimum":0,"maximum":9007199254740991},"remaining":{"type":"integer","minimum":0,"maximum":9007199254740991},"percent_used":{"type":"number","minimum":0},"retention_days":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["cap","used","remaining","percent_used","retention_days"],"additionalProperties":false}},"required":["site_id","added","usage"],"additionalProperties":false}}}},"402":{"description":"Payment Required"}}}},"/api/sites":{"get":{"operationId":"sites","summary":"List all sites owned by the authenticated wallet.","tags":["Sites"],"security":[{"siwx":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"sites":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"domain":{"anyOf":[{"type":"string"},{"type":"null"}]},"status":{"type":"string","enum":["active","archived","exhausted"]},"usage":{"type":"object","properties":{"cap":{"type":"integer","minimum":0,"maximum":9007199254740991},"used":{"type":"integer","minimum":0,"maximum":9007199254740991},"remaining":{"type":"integer","minimum":0,"maximum":9007199254740991},"percent_used":{"type":"number","minimum":0},"retention_days":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["cap","used","remaining","percent_used","retention_days"],"additionalProperties":false},"created_at":{"type":"string"},"last_active_at":{"anyOf":[{"type":"string"},{"type":"null"}]},"visits_total":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["id","name","domain","status","usage","created_at","last_active_at","visits_total"],"additionalProperties":false}}},"required":["sites"],"additionalProperties":false}}}},"402":{"description":"Authentication Required"}}}},"/api/site":{"get":{"operationId":"site","summary":"Fetch a single site and its usage summary.","tags":["Site"],"security":[{"siwx":[]}],"parameters":[{"in":"query","name":"id","schema":{"type":"string","minLength":1},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"site":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"domain":{"anyOf":[{"type":"string"},{"type":"null"}]},"status":{"type":"string","enum":["active","archived","exhausted"]},"public_token":{"type":"string"},"created_at":{"type":"string"}},"required":["id","name","domain","status","public_token","created_at"],"additionalProperties":false},"usage":{"type":"object","properties":{"cap":{"type":"integer","minimum":0,"maximum":9007199254740991},"used":{"type":"integer","minimum":0,"maximum":9007199254740991},"remaining":{"type":"integer","minimum":0,"maximum":9007199254740991},"percent_used":{"type":"number","minimum":0},"retention_days":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["cap","used","remaining","percent_used","retention_days"],"additionalProperties":false}},"required":["site","usage"],"additionalProperties":false}}}},"402":{"description":"Authentication Required"}}}},"/api/site/token/rotate":{"post":{"operationId":"site_token_rotate","summary":"Rotate a site public token with a short overlap window.","tags":["Site"],"security":[{"siwx":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"site_id":{"type":"string","minLength":1}},"required":["site_id"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"site_id":{"type":"string"},"public_token":{"type":"string"}},"required":["site_id","public_token"],"additionalProperties":false}}}},"402":{"description":"Authentication Required"}}}},"/api/site/archive":{"post":{"operationId":"site_archive","summary":"Archive a site and stop accepting new tracked requests.","tags":["Site"],"security":[{"siwx":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"site_id":{"type":"string","minLength":1}},"required":["site_id"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","const":true},"site_id":{"type":"string"},"status":{"type":"string","const":"archived"}},"required":["success","site_id","status"],"additionalProperties":false}}}},"402":{"description":"Authentication Required"}}}},"/api/site/domain":{"post":{"operationId":"site_domain","summary":"Set or update the tracked domain for a site. Required before ingest will accept hits.","tags":["Site"],"security":[{"siwx":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"site_id":{"type":"string","minLength":1},"domain":{"type":"string","minLength":1,"maxLength":255}},"required":["site_id","domain"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"site_id":{"type":"string"},"domain":{"type":"string"}},"required":["site_id","domain"],"additionalProperties":false}}}},"402":{"description":"Authentication Required"}}}},"/api/site/overview":{"get":{"operationId":"site_overview","summary":"Get visits-over-time overview for a site.","tags":["Site"],"security":[{"siwx":[]}],"parameters":[{"in":"query","name":"site_id","schema":{"type":"string","minLength":1},"required":true},{"in":"query","name":"period","schema":{"default":"7d","type":"string","enum":["24h","7d","30d"]}},{"in":"query","name":"limit","schema":{"default":100,"type":"integer","minimum":1,"maximum":500}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"site_id":{"type":"string"},"period":{"type":"string"},"totals":{"type":"object","properties":{"visits":{"type":"integer","minimum":0,"maximum":9007199254740991},"unique_ips_estimate":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["visits","unique_ips_estimate"],"additionalProperties":false},"series":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"visits":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["date","visits"],"additionalProperties":false}}},"required":["site_id","period","totals","series"],"additionalProperties":false}}}},"402":{"description":"Authentication Required"}}}},"/api/site/pages":{"get":{"operationId":"site_pages","summary":"Get top pages for a site.","tags":["Site"],"security":[{"siwx":[]}],"parameters":[{"in":"query","name":"site_id","schema":{"type":"string","minLength":1},"required":true},{"in":"query","name":"period","schema":{"default":"7d","type":"string","enum":["24h","7d","30d"]}},{"in":"query","name":"limit","schema":{"default":100,"type":"integer","minimum":1,"maximum":500}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"rows":{"type":"array","items":{"type":"object","properties":{"pathname":{"type":"string"},"visits":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["pathname","visits"],"additionalProperties":false}}},"required":["rows"],"additionalProperties":false}}}},"402":{"description":"Authentication Required"}}}},"/api/site/sources":{"get":{"operationId":"site_sources","summary":"Get referrer and UTM breakdowns for a site.","tags":["Site"],"security":[{"siwx":[]}],"parameters":[{"in":"query","name":"site_id","schema":{"type":"string","minLength":1},"required":true},{"in":"query","name":"period","schema":{"default":"7d","type":"string","enum":["24h","7d","30d"]}},{"in":"query","name":"limit","schema":{"default":100,"type":"integer","minimum":1,"maximum":500}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"referrers":{"type":"array","items":{"type":"object","properties":{"referrer":{"anyOf":[{"type":"string"},{"type":"null"}]},"visits":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["referrer","visits"],"additionalProperties":false}},"utm_sources":{"type":"array","items":{"type":"object","properties":{"utm_source":{"anyOf":[{"type":"string"},{"type":"null"}]},"visits":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["utm_source","visits"],"additionalProperties":false}},"utm_campaigns":{"type":"array","items":{"type":"object","properties":{"utm_campaign":{"anyOf":[{"type":"string"},{"type":"null"}]},"visits":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["utm_campaign","visits"],"additionalProperties":false}}},"required":["referrers","utm_sources","utm_campaigns"],"additionalProperties":false}}}},"402":{"description":"Authentication Required"}}}},"/api/site/tech":{"get":{"operationId":"site_tech","summary":"Get browser, OS, device, and country breakdowns for a site.","tags":["Site"],"security":[{"siwx":[]}],"parameters":[{"in":"query","name":"site_id","schema":{"type":"string","minLength":1},"required":true},{"in":"query","name":"period","schema":{"default":"7d","type":"string","enum":["24h","7d","30d"]}},{"in":"query","name":"limit","schema":{"default":100,"type":"integer","minimum":1,"maximum":500}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"browsers":{"type":"array","items":{"type":"object","properties":{"browser":{"type":"string"},"visits":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["browser","visits"],"additionalProperties":false}},"os":{"type":"array","items":{"type":"object","properties":{"os":{"type":"string"},"visits":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["os","visits"],"additionalProperties":false}},"devices":{"type":"array","items":{"type":"object","properties":{"device":{"type":"string"},"visits":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["device","visits"],"additionalProperties":false}},"countries":{"type":"array","items":{"type":"object","properties":{"country":{"anyOf":[{"type":"string"},{"type":"null"}]},"visits":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["country","visits"],"additionalProperties":false}}},"required":["browsers","os","devices","countries"],"additionalProperties":false}}}},"402":{"description":"Authentication Required"}}}},"/api/site/visits":{"get":{"operationId":"site_visits","summary":"Get recent visits for a site.","tags":["Site"],"security":[{"siwx":[]}],"parameters":[{"in":"query","name":"site_id","schema":{"type":"string","minLength":1},"required":true},{"in":"query","name":"period","schema":{"default":"7d","type":"string","enum":["24h","7d","30d"]}},{"in":"query","name":"limit","schema":{"default":100,"type":"integer","minimum":1,"maximum":500}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"rows":{"type":"array","items":{"type":"object","properties":{"timestamp":{"type":"string"},"pathname":{"type":"string"},"referrer":{"anyOf":[{"type":"string"},{"type":"null"}]},"utm_source":{"anyOf":[{"type":"string"},{"type":"null"}]},"browser":{"type":"string"},"os":{"type":"string"},"device":{"type":"string"},"country":{"anyOf":[{"type":"string"},{"type":"null"}]},"ip_prefix":{"type":"string"}},"required":["timestamp","pathname","referrer","utm_source","browser","os","device","country","ip_prefix"],"additionalProperties":false}}},"required":["rows"],"additionalProperties":false}}}},"402":{"description":"Authentication Required"}}}}},"components":{"securitySchemes":{"siwx":{"type":"apiKey","in":"header","name":"SIGN-IN-WITH-X"}}}}