{
  "openapi": "3.1.0",
  "info": {
    "title": "9ping Analytics API",
    "version": "1.0.0",
    "description": "Machine-readable API for 9ping Analytics (9ping站长统计). Covers client-side tracking (collect), server-side events (s2s), raw event export, and read reports. For AI agents integrating analytics: use /s2s.php for backend/offline events (orders, refunds, app callbacks), /export_raw.php for ETL into a data warehouse, and /api.php for reading aggregated reports. Authentication for write/export is a per-user API key (tjk_...) found in the account settings.",
    "contact": { "name": "9ping Analytics", "url": "https://9ping.dev" },
    "license": { "name": "Proprietary", "url": "https://9ping.dev/terms" }
  },
  "servers": [ { "url": "https://9ping.dev", "description": "Production" } ],
  "components": {
    "securitySchemes": {
      "ApiKeyBearer": { "type": "http", "scheme": "bearer", "bearerFormat": "tjk_*", "description": "Per-user API key. Send as `Authorization: Bearer tjk_xxx` (or `X-API-Key: tjk_xxx`)." }
    },
    "schemas": {
      "Item": {
        "type": "object",
        "description": "One e-commerce line item.",
        "properties": {
          "sku": { "type": "string", "description": "Product SKU / id.", "example": "SKU-NIKE-AF1" },
          "name": { "type": "string", "example": "Air Force 1" },
          "qty": { "type": "integer", "minimum": 1, "example": 1 },
          "price": { "type": "number", "description": "Unit price in the event's currency.", "example": 129.0 }
        },
        "required": ["sku"]
      },
      "S2SEvent": {
        "type": "object",
        "properties": {
          "sid": { "type": "string", "description": "Site id (data-sid).", "example": "demo0001" },
          "type": { "type": "string", "enum": ["pv","event","leave","perf","vital","engaged"], "default": "event" },
          "path": { "type": "string", "example": "/checkout" },
          "ec": { "type": "string", "description": "Event category.", "example": "ecommerce" },
          "ea": { "type": "string", "description": "Event action.", "example": "place_order" },
          "el": { "type": "string", "description": "Event label.", "example": "espn_shop" },
          "rev": { "type": "number", "description": "Revenue amount (raw, in `cur`).", "example": 129.0 },
          "cur": { "type": "string", "description": "ISO 4217 currency; normalized to USD server-side.", "example": "USD" },
          "uid": { "type": "string", "description": "Your logged-in user id (HASH it before sending). Enables cross-device User-ID stitching.", "example": "u_8f3a2c" },
          "items": { "type": "array", "items": { "$ref": "#/components/schemas/Item" } },
          "vid": { "type": "string", "description": "Visitor id (optional; defaults to uid or 's2s')." },
          "sess": { "type": "string", "description": "Session id (optional)." },
          "ts": { "type": "integer", "description": "Unix seconds. Allows backfill; default now. Range 2000-01-01 .. now+5min." },
          "cd1": { "type": "string" }, "cd2": { "type": "string" }, "cd3": { "type": "string" },
          "utm_source": { "type": "string" }, "utm_medium": { "type": "string" }, "utm_campaign": { "type": "string" },
          "country": { "type": "string" }, "os": { "type": "string" }, "browser": { "type": "string" }, "device": { "type": "string" }
        },
        "required": ["sid"]
      }
    }
  },
  "paths": {
    "/collect.php": {
      "get": {
        "summary": "Client-side hit (pixel / beacon)",
        "description": "Records a page view or event from the browser. Normally called by the /hm.min.js tag; document here for completeness. No auth (validated by site_id + optional Origin). Returns 204 (or a 1x1 GIF when `_` timestamp is present).",
        "parameters": [
          { "name": "sid", "in": "query", "required": true, "schema": { "type": "string" } },
          { "name": "type", "in": "query", "schema": { "type": "string", "enum": ["pv","event","leave","perf","vital","engaged"], "default": "pv" } },
          { "name": "url", "in": "query", "schema": { "type": "string" } },
          { "name": "ref", "in": "query", "schema": { "type": "string" } },
          { "name": "vid", "in": "query", "schema": { "type": "string" } },
          { "name": "sess", "in": "query", "schema": { "type": "string" } },
          { "name": "ec", "in": "query", "schema": { "type": "string" } },
          { "name": "ea", "in": "query", "schema": { "type": "string" } },
          { "name": "el", "in": "query", "schema": { "type": "string" } },
          { "name": "rev", "in": "query", "schema": { "type": "number" } },
          { "name": "cur", "in": "query", "schema": { "type": "string" } },
          { "name": "uid", "in": "query", "schema": { "type": "string" } },
          { "name": "items", "in": "query", "schema": { "type": "string", "description": "JSON-encoded array of Item." } },
          { "name": "cd1", "in": "query", "schema": { "type": "string" } },
          { "name": "cd2", "in": "query", "schema": { "type": "string" } },
          { "name": "cd3", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": { "204": { "description": "Recorded (empty body)" } }
      },
      "post": {
        "summary": "Client-side hit (POST / sendBeacon)",
        "description": "Same as GET but via urlencoded body; used by navigator.sendBeacon.",
        "requestBody": { "content": { "application/x-www-form-urlencoded": { "schema": { "type": "object" } } } },
        "responses": { "204": { "description": "Recorded" } }
      }
    },
    "/s2s.php": {
      "post": {
        "summary": "Server-side events (Measurement Protocol equivalent)",
        "description": "Send backend / offline / app events directly, bypassing the browser. Ideal for orders, refunds, subscription renewals, CRM callbacks. Body is a single event or {\"events\":[...]} (max 500). Revenue is normalized to USD by nightly FX. Rate limit 600/min per key.",
        "security": [ { "ApiKeyBearer": [] } ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  { "$ref": "#/components/schemas/S2SEvent" },
                  { "type": "object", "properties": { "events": { "type": "array", "maxItems": 500, "items": { "$ref": "#/components/schemas/S2SEvent" } } }, "required": ["events"] }
                ]
              },
              "examples": {
                "single_order": { "value": { "sid": "demo0001", "type": "event", "ec": "ecommerce", "ea": "place_order", "rev": 129.0, "cur": "USD", "uid": "u_8f3a", "items": [ { "sku": "SKU-NIKE-AF1", "name": "Air Force 1", "qty": 1, "price": 129.0 } ] } },
                "batch": { "value": { "events": [ { "sid": "demo0001", "ec": "refund", "ea": "processed", "rev": 50, "cur": "USD" }, { "sid": "demo0001", "type": "pv", "path": "/server-rendered" } ] } }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Ingested", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "inserted": { "type": "integer" }, "rejected": { "type": "integer" } } } } } },
          "401": { "description": "Missing API key" },
          "403": { "description": "Invalid API key or no access to site" },
          "429": { "description": "Rate limited" }
        }
      }
    },
    "/export_raw.php": {
      "get": {
        "summary": "Raw event export (data-warehouse ETL)",
        "description": "Stream raw hit rows for a time window as NDJSON or CSV. Use for periodic ETL into BigQuery / S3 / Postgres. Rate limit 30/min per key.",
        "security": [ { "ApiKeyBearer": [] } ],
        "parameters": [
          { "name": "sid", "in": "query", "required": true, "schema": { "type": "string" } },
          { "name": "from", "in": "query", "schema": { "type": "string", "description": "YYYY-MM-DD or unix seconds. Default: 7 days ago." } },
          { "name": "to", "in": "query", "schema": { "type": "string", "description": "YYYY-MM-DD or unix seconds. Default: now." } },
          { "name": "format", "in": "query", "schema": { "type": "string", "enum": ["ndjson","csv"], "default": "ndjson" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 100000, "maximum": 500000 } },
          { "name": "key", "in": "query", "schema": { "type": "string", "description": "Alternative to Authorization header." } }
        ],
        "responses": {
          "200": { "description": "Stream of rows", "content": { "application/x-ndjson": {}, "text/csv": {} } },
          "401": { "description": "Missing API key" }, "403": { "description": "No access to site" }
        }
      }
    },
    "/api.php": {
      "get": {
        "summary": "Read aggregated reports",
        "description": "Returns aggregated JSON reports. Session-cookie auth for private sites; public demo sites (e.g. demo0001) are open. For programmatic reads prefer /export_raw.php (API-key). `action` selects the report.",
        "parameters": [
          { "name": "action", "in": "query", "required": true, "schema": { "type": "string", "enum": ["overview","trend","sources","engines","socials","referrers","geo","pages","keywords","events","site_search","not_found","entrypages","exitpages","byhour","newret","browsers","os","depthdist","durationdist","vitals","campaigns","goals","funnels","cohorts","user_paths","custom_dims","revenue","skus","userid","realtime","sites","online"] } },
          { "name": "sid", "in": "query", "schema": { "type": "string", "default": "demo0001" } },
          { "name": "days", "in": "query", "schema": { "type": "integer", "default": 7, "minimum": 1, "maximum": 90 } }
        ],
        "responses": { "200": { "description": "Report JSON (shape varies by action)" }, "403": { "description": "Forbidden" } }
      }
    }
  }
}
