{
  "openapi": "3.1.0",
  "info": {
    "title": "Observe API",
    "description": "AI cost observability and profitability analytics. Track LLM costs per customer and feature, compute margins, and get pricing recommendations.\n\n## Authentication\n\nTwo auth methods:\n\n- **SDK Key** — For event ingestion and gateway proxy. Generate in the dashboard under Settings > SDK Keys. Pass as `Authorization: Bearer obs_your_key`.\n- **Clerk JWT** — For dashboard endpoints. Obtained via Clerk authentication flow.\n\n## Rate Limits\n\n| Endpoint | Limit |\n|----------|-------|\n| Auth | 20 requests / 15 min |\n| API | 60 requests / min |\n| Insights | 5 requests / min |",
    "version": "1.0.0",
    "contact": {
      "name": "Tanso Support",
      "url": "https://tansohq.com",
      "email": "support@tansohq.com"
    }
  },
  "servers": [
    {
      "url": "https://observe.tansohq.com/api",
      "description": "Production"
    }
  ],
  "security": [
    {
      "SdkKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Events",
      "description": "Ingest cost, usage, and revenue events from your application. Events feed margin analytics, alerts, and billing calculations."
    },
    {
      "name": "Gateway",
      "description": "Proxy AI provider calls (OpenAI, Anthropic) through Observe to automatically capture cost data. Drop-in replacement — swap the base URL and add your Observe key."
    },
    {
      "name": "Analytics",
      "description": "Customer profitability, margin analysis, and cost breakdowns."
    },
    {
      "name": "Alerts",
      "description": "Configure cost and margin alert rules. 14 metric types across 5 categories."
    },
    {
      "name": "Models",
      "description": "AI model pricing data and cost-per-token lookups."
    }
  ],
  "paths": {
    "/events/ingest": {
      "post": {
        "tags": ["Events"],
        "operationId": "ingestEvents",
        "summary": "Ingest usage events",
        "description": "Batch-ingest cost, usage, and revenue events. Up to 1000 events per request. Events are idempotent when `idempotencyKey` is provided.\n\nAuthentication: SDK API key via `Authorization: Bearer` header.",
        "security": [
          {
            "SdkKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventIngestRequest"
              },
              "example": {
                "events": [
                  {
                    "eventName": "inference",
                    "customerReferenceId": "acme-corp",
                    "featureKey": "chat",
                    "costAmount": 0.003,
                    "model": "gpt-4o-mini",
                    "modelProvider": "openai",
                    "usageUnits": 1,
                    "inputTokens": 500,
                    "outputTokens": 150,
                    "idempotencyKey": "evt_abc123"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Events processed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventIngestResponse"
                },
                "example": {
                  "accepted": 1,
                  "rejected": 0,
                  "errors": []
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — missing required fields or batch exceeds 1000.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing SDK key."
          },
          "429": {
            "description": "Monthly event limit reached or rate limited."
          }
        }
      }
    },
    "/v1/chat/completions": {
      "post": {
        "tags": ["Gateway"],
        "operationId": "proxyChatCompletions",
        "summary": "OpenAI-compatible chat completions proxy",
        "description": "Drop-in replacement for OpenAI's `/v1/chat/completions`. Routes requests to your configured AI provider and automatically logs cost, latency, and token usage as Observe events.\n\nSupports streaming via `stream: true`.\n\nPass your provider API key in the `Authorization` header and your Observe SDK key in `X-Observe-Key`.",
        "security": [
          {
            "SdkKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "X-Observe-Key",
            "in": "header",
            "required": true,
            "description": "Your Observe SDK key for event attribution.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Observe-Customer",
            "in": "header",
            "required": false,
            "description": "Customer identifier for cost attribution.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Observe-Feature",
            "in": "header",
            "required": false,
            "description": "Feature key for cost attribution.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatCompletionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Chat completion response (or SSE stream if `stream: true`).",
            "headers": {
              "X-Observe-Provider": {
                "description": "Provider used (e.g., openai, anthropic).",
                "schema": {
                  "type": "string"
                }
              },
              "X-Observe-Model": {
                "description": "Model used.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatCompletionResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Observe key."
          },
          "502": {
            "description": "All routing targets exhausted."
          }
        }
      }
    },
    "/v1/embeddings": {
      "post": {
        "tags": ["Gateway"],
        "operationId": "proxyEmbeddings",
        "summary": "OpenAI-compatible embeddings proxy",
        "description": "Proxy to OpenAI embeddings API. Logs cost as an Observe event.",
        "security": [
          {
            "SdkKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "X-Observe-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["input", "model"],
                "properties": {
                  "input": {
                    "oneOf": [
                      { "type": "string" },
                      { "type": "array", "items": { "type": "string" } }
                    ]
                  },
                  "model": {
                    "type": "string",
                    "example": "text-embedding-3-small"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Embeddings response."
          }
        }
      }
    },
    "/v1/messages": {
      "post": {
        "tags": ["Gateway"],
        "operationId": "proxyAnthropicMessages",
        "summary": "Anthropic-compatible messages proxy",
        "description": "Proxy to Anthropic Messages API. Logs cost as an Observe event.",
        "security": [
          {
            "SdkKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "X-Observe-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["model", "messages"],
                "properties": {
                  "model": {
                    "type": "string",
                    "example": "claude-sonnet-4-20250514"
                  },
                  "messages": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "role": {
                          "type": "string",
                          "enum": ["user", "assistant"]
                        },
                        "content": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "max_tokens": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Anthropic messages response."
          }
        }
      }
    },
    "/events": {
      "get": {
        "tags": ["Events"],
        "operationId": "listEvents",
        "summary": "List events",
        "description": "List events for the current account. Supports pagination.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Event list."
          }
        }
      }
    },
    "/events/by-feature": {
      "get": {
        "tags": ["Analytics"],
        "operationId": "eventsByFeature",
        "summary": "Events aggregated by feature",
        "responses": {
          "200": {
            "description": "Feature-level aggregation."
          }
        }
      }
    },
    "/events/by-customer": {
      "get": {
        "tags": ["Analytics"],
        "operationId": "eventsByCustomer",
        "summary": "Events aggregated by customer",
        "responses": {
          "200": {
            "description": "Customer-level aggregation."
          }
        }
      }
    },
    "/events/by-model": {
      "get": {
        "tags": ["Analytics"],
        "operationId": "eventsByModel",
        "summary": "Events aggregated by model",
        "responses": {
          "200": {
            "description": "Model-level aggregation."
          }
        }
      }
    },
    "/events/by-cost-type": {
      "get": {
        "tags": ["Analytics"],
        "operationId": "eventsByCostType",
        "summary": "Cost breakdown by type",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 30
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cost type breakdown.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "breakdown": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "cost_type": { "type": "string" },
                          "event_count": { "type": "integer" },
                          "total_cost": { "type": "number" },
                          "total_revenue": { "type": "number" },
                          "total_usage": { "type": "number" }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/events/traces": {
      "get": {
        "tags": ["Events"],
        "operationId": "listTraces",
        "summary": "List traces with aggregated metrics",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated trace list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "traces": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TraceSummary"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/analytics/customer-pnl": {
      "get": {
        "tags": ["Analytics"],
        "operationId": "customerPnl",
        "summary": "Customer profit & loss breakdown",
        "description": "Per-customer revenue, costs, and margin analysis.",
        "responses": {
          "200": {
            "description": "Customer P&L data."
          }
        }
      }
    },
    "/analytics/margin-alerts": {
      "get": {
        "tags": ["Analytics"],
        "operationId": "marginAlerts",
        "summary": "Margin alert detection",
        "description": "Detect features or customers with concerning margin trends.",
        "responses": {
          "200": {
            "description": "Margin alert data."
          }
        }
      }
    },
    "/alerts": {
      "get": {
        "tags": ["Alerts"],
        "operationId": "listAlerts",
        "summary": "List alert rules",
        "responses": {
          "200": {
            "description": "Alert rules list."
          }
        }
      },
      "post": {
        "tags": ["Alerts"],
        "operationId": "createAlert",
        "summary": "Create an alert rule",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AlertRule"
              },
              "example": {
                "name": "High daily cost",
                "metric": "daily_cost",
                "operator": ">",
                "threshold": 50.0,
                "email": "alerts@example.com"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Alert rule created."
          }
        }
      }
    },
    "/alerts/{id}": {
      "put": {
        "tags": ["Alerts"],
        "operationId": "updateAlert",
        "summary": "Update an alert rule",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AlertRule"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Alert rule updated."
          }
        }
      },
      "delete": {
        "tags": ["Alerts"],
        "operationId": "deleteAlert",
        "summary": "Delete an alert rule",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Alert rule deleted."
          }
        }
      }
    },
    "/models": {
      "get": {
        "tags": ["Models"],
        "operationId": "listModels",
        "summary": "List AI models with pricing",
        "responses": {
          "200": {
            "description": "Model list with cost-per-token data."
          }
        }
      }
    },
    "/pricing/models": {
      "get": {
        "tags": ["Models"],
        "operationId": "modelPricing",
        "summary": "Model pricing table",
        "description": "Cost per token by model.",
        "responses": {
          "200": {
            "description": "Model pricing data."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "SdkKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "SDK API key generated in the Observe dashboard. Pass as `Authorization: Bearer obs_your_key`."
      },
      "ClerkAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Clerk JWT for dashboard authentication. Obtained via Clerk's authentication flow."
      }
    },
    "schemas": {
      "EventIngestRequest": {
        "type": "object",
        "required": ["events"],
        "properties": {
          "events": {
            "type": "array",
            "maxItems": 1000,
            "items": {
              "$ref": "#/components/schemas/Event"
            }
          }
        }
      },
      "Event": {
        "type": "object",
        "required": ["eventName", "customerReferenceId", "featureKey"],
        "properties": {
          "eventName": {
            "type": "string",
            "description": "Name of the event (e.g., inference, api_call)."
          },
          "customerReferenceId": {
            "type": "string",
            "description": "Your customer identifier for cost attribution."
          },
          "featureKey": {
            "type": "string",
            "description": "Feature key for grouping (e.g., chat, search)."
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp. Defaults to now."
          },
          "costAmount": {
            "type": "number",
            "description": "Provider cost in USD."
          },
          "costUnit": {
            "type": "string",
            "default": "usd"
          },
          "revenueAmount": {
            "type": "number",
            "description": "Revenue attributed to this event."
          },
          "usageUnits": {
            "type": "number",
            "description": "Usage quantity (API calls, tokens, etc.)."
          },
          "model": {
            "type": "string",
            "description": "AI model name (e.g., gpt-4o, claude-sonnet-4-20250514)."
          },
          "modelProvider": {
            "type": "string",
            "description": "AI provider (e.g., openai, anthropic)."
          },
          "inputTokens": {
            "type": "integer",
            "description": "Input token count."
          },
          "outputTokens": {
            "type": "integer",
            "description": "Output token count."
          },
          "properties": {
            "type": "object",
            "additionalProperties": true,
            "description": "Custom properties for filtering and grouping."
          },
          "meta": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "idempotencyKey": {
            "type": "string",
            "description": "Unique key for deduplication."
          },
          "traceId": {
            "type": "string",
            "description": "Trace ID for distributed tracing."
          },
          "spanId": {
            "type": "string",
            "description": "Span ID within a trace."
          },
          "parentSpanId": {
            "type": "string",
            "description": "Parent span ID for trace hierarchy."
          },
          "durationMs": {
            "type": "number",
            "description": "Operation duration in milliseconds."
          },
          "costType": {
            "type": "string",
            "description": "Cost category (e.g., llm, vector_db, compute). Defaults to 'llm' if model is provided."
          }
        }
      },
      "EventIngestResponse": {
        "type": "object",
        "properties": {
          "accepted": {
            "type": "integer",
            "description": "Number of events accepted."
          },
          "rejected": {
            "type": "integer",
            "description": "Number of events rejected."
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "index": { "type": "integer" },
                "error": { "type": "string" }
              }
            }
          }
        }
      },
      "ChatCompletionRequest": {
        "type": "object",
        "required": ["messages"],
        "properties": {
          "model": {
            "type": "string",
            "description": "Model to use. Falls back to gateway config default."
          },
          "messages": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["role", "content"],
              "properties": {
                "role": {
                  "type": "string",
                  "enum": ["user", "assistant", "system"]
                },
                "content": {
                  "type": "string"
                }
              }
            }
          },
          "temperature": { "type": "number" },
          "max_tokens": { "type": "integer" },
          "stream": {
            "type": "boolean",
            "default": false,
            "description": "If true, returns Server-Sent Events."
          }
        }
      },
      "ChatCompletionResponse": {
        "type": "object",
        "properties": {
          "choices": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "message": {
                  "type": "object",
                  "properties": {
                    "role": { "type": "string" },
                    "content": { "type": "string" }
                  }
                }
              }
            }
          },
          "model": { "type": "string" },
          "usage": {
            "type": "object",
            "properties": {
              "prompt_tokens": { "type": "integer" },
              "completion_tokens": { "type": "integer" },
              "total_tokens": { "type": "integer" }
            }
          }
        }
      },
      "TraceSummary": {
        "type": "object",
        "properties": {
          "trace_id": { "type": "string" },
          "start_time": { "type": "string", "format": "date-time" },
          "span_count": { "type": "integer" },
          "total_cost": { "type": "number" },
          "total_revenue": { "type": "number" },
          "total_duration_ms": { "type": "number" },
          "root_event": { "type": "string" },
          "cost_types": {
            "type": "array",
            "items": { "type": "string" }
          }
        }
      },
      "AlertRule": {
        "type": "object",
        "required": ["name", "metric", "operator", "threshold", "email"],
        "properties": {
          "name": { "type": "string" },
          "metric": {
            "type": "string",
            "enum": [
              "daily_cost",
              "cost_per_event",
              "margin_percent",
              "customer_margin",
              "feature_margin_trend",
              "customer_cost_vs_revenue",
              "model_cost_spike",
              "margin_compression",
              "usage_velocity",
              "customer_cost_share",
              "credit_burn_rate",
              "top_customer_unprofitable",
              "feature_cost_disparity",
              "model_cost_increase",
              "customer_concentration",
              "provider_concentration",
              "model_concentration"
            ]
          },
          "operator": {
            "type": "string",
            "enum": [">", "<", ">=", "<=", "=="]
          },
          "threshold": { "type": "number" },
          "email": {
            "type": "string",
            "format": "email"
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable error message."
          }
        }
      }
    }
  }
}
