{
  "openapi": "3.1.0",
  "info": {
    "title": "StackSpend Public API",
    "version": "1.0.0",
    "description": "Programmatic access to your StackSpend cost data and push-ingest endpoints.\n\nAll endpoints are authenticated with an **API key** (prefix `ss_live_`) created under Settings → API in the StackSpend dashboard. Pass it as a bearer token (`Authorization: Bearer ss_live_…`) or in the `X-API-Key` header. Each key carries one or more scopes; calling an endpoint without its required scope returns `INVALID_SCOPE`.\n\nResponses use a standard envelope: `{ \"data\": ..., \"error\": ..., \"meta\": ... }`.",
    "contact": { "name": "StackSpend", "url": "https://www.stackspend.app" }
  },
  "servers": [{ "url": "https://ingest.stackspend.app", "description": "Production" }],
  "security": [{ "bearerAuth": [] }, { "apiKeyHeader": [] }],
  "tags": [
    { "name": "Cost data", "description": "Read cost line items, rollups, and anomalies." },
    { "name": "Ingest", "description": "Push usage/cost data into StackSpend." }
  ],
  "paths": {
    "/api/v1/public/line-items": {
      "get": {
        "operationId": "listLineItems",
        "tags": ["Cost data"],
        "summary": "List cost line items",
        "description": "Cursor-paginated cost line items. Requires the `line_items:read` scope.",
        "parameters": [
          { "name": "start_date", "in": "query", "required": true, "schema": { "type": "string", "format": "date" }, "description": "Inclusive start (YYYY-MM-DD)." },
          { "name": "end_date", "in": "query", "required": true, "schema": { "type": "string", "format": "date" }, "description": "Inclusive end (YYYY-MM-DD)." },
          { "name": "provider", "in": "query", "schema": { "type": "string" }, "description": "Filter by provider_type (e.g. aws, openai, custom)." },
          { "name": "service", "in": "query", "schema": { "type": "string" } },
          { "name": "category", "in": "query", "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50, "maximum": 1000 } },
          { "name": "cursor", "in": "query", "schema": { "type": "string" }, "description": "Opaque pagination cursor from the previous response." }
        ],
        "responses": {
          "200": {
            "description": "A page of line items.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "items": { "type": "array", "items": { "$ref": "#/components/schemas/LineItem" } },
                        "meta": {
                          "type": "object",
                          "properties": {
                            "next_cursor": { "type": ["string", "null"] },
                            "has_more": { "type": "boolean" }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/api/v1/public/rollups/daily": {
      "get": {
        "operationId": "getDailyRollups",
        "tags": ["Cost data"],
        "summary": "Daily cost rollups",
        "description": "Pre-aggregated daily cost, grouped by a dimension. Requires the `rollups:read` scope.",
        "parameters": [
          { "name": "start_date", "in": "query", "required": true, "schema": { "type": "string", "format": "date" } },
          { "name": "end_date", "in": "query", "required": true, "schema": { "type": "string", "format": "date" } },
          { "name": "group_by", "in": "query", "schema": { "type": "string", "default": "provider", "enum": ["provider", "service", "category", "total"] } }
        ],
        "responses": {
          "200": {
            "description": "Daily rollups.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/Rollup" } },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "group_by": { "type": "string" },
                        "range": { "type": "object", "properties": { "start": { "type": "string" }, "end": { "type": "string" } } }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/api/v1/public/anomalies": {
      "get": {
        "operationId": "listAnomalies",
        "tags": ["Cost data"],
        "summary": "List anomaly alerts",
        "description": "Detected spend anomalies. Requires the `anomalies:read` scope.",
        "parameters": [
          { "name": "provider_type", "in": "query", "schema": { "type": "string" } },
          { "name": "severity", "in": "query", "schema": { "type": "string", "enum": ["low", "medium", "high", "critical"] } },
          { "name": "status", "in": "query", "schema": { "type": "string" } },
          { "name": "since", "in": "query", "schema": { "type": "string", "format": "date-time" }, "description": "Only anomalies created after this ISO 8601 timestamp." },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 10 } }
        ],
        "responses": {
          "200": {
            "description": "Anomaly alerts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Anomaly" } } }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/api/v1/public/ingest/otel/{provider}/{connectionId}": {
      "post": {
        "operationId": "ingestOtel",
        "tags": ["Ingest"],
        "summary": "Push OpenTelemetry usage (Claude Code / Codex)",
        "description": "Ingest OTLP metric/log payloads for an agent connection. Requires the `usage:write` scope.",
        "parameters": [
          { "name": "provider", "in": "path", "required": true, "schema": { "type": "string", "enum": ["claude", "claude-code", "codex"] } },
          { "name": "connectionId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
        ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "description": "OTLP JSON (resourceMetrics / resourceLogs)." } } }
        },
        "responses": {
          "202": { "$ref": "#/components/responses/IngestAccepted" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/v1/public/ingest/focus/{connectionId}": {
      "post": {
        "operationId": "ingestFocus",
        "tags": ["Ingest"],
        "summary": "Push custom-provider cost (StackSpend FOCUS Profile)",
        "description": "Submit fully-priced cost rows for a custom provider using the StackSpend FOCUS Profile v1 (a profile of the FinOps FOCUS 1.2 schema). Requires the `usage:write` scope. Create the custom provider in Settings → Integrations to get its connectionId and ingest key.",
        "parameters": [
          { "name": "connectionId", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/FocusIngestBody" },
              "example": {
                "format": "stackspend.focus",
                "version": "1",
                "rows": [
                  {
                    "ChargePeriodStart": "2026-06-01",
                    "ChargePeriodEnd": "2026-06-02",
                    "BilledCost": 12.5,
                    "EffectiveCost": 12.5,
                    "BillingCurrency": "USD",
                    "ChargeCategory": "Usage",
                    "ServiceName": "Compute",
                    "RegionId": "us-east-1",
                    "Tags": { "env": "prod" }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "202": { "$ref": "#/components/responses/IngestAccepted" },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "description": "The connection is inactive.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": { "type": "http", "scheme": "bearer", "description": "API key as a bearer token: `Authorization: Bearer ss_live_…`" },
      "apiKeyHeader": { "type": "apiKey", "in": "header", "name": "X-API-Key", "description": "API key in the `X-API-Key` header." }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "data": { "type": "null" },
          "error": {
            "type": "object",
            "properties": { "code": { "type": "string" }, "message": { "type": "string" } }
          }
        }
      },
      "LineItem": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "usage_date": { "type": "string", "format": "date" },
          "usage_month": { "type": "string" },
          "provider_type": { "type": "string" },
          "provider_service": { "type": ["string", "null"] },
          "provider_account_id": { "type": ["string", "null"] },
          "project_id": { "type": ["string", "null"] },
          "user_email": { "type": ["string", "null"] },
          "net_amount_usd": { "type": "number" },
          "currency_original": { "type": "string" },
          "usage_quantity": { "type": ["number", "null"] },
          "usage_unit": { "type": ["string", "null"] }
        }
      },
      "Rollup": {
        "type": "object",
        "description": "A daily rollup row. Includes the grouped dimension plus aggregates.",
        "properties": {
          "usage_date": { "type": "string", "format": "date" },
          "cost_usd": { "type": "number" },
          "usage_quantity": { "type": ["number", "null"] }
        },
        "additionalProperties": true
      },
      "Anomaly": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "provider_type": { "type": ["string", "null"] },
          "service": { "type": ["string", "null"] },
          "category": { "type": ["string", "null"] },
          "detected_date": { "type": "string", "format": "date" },
          "actual_cost": { "type": "number" },
          "expected_cost": { "type": "number" },
          "deviation_percent": { "type": "number" },
          "severity": { "type": "string", "enum": ["low", "medium", "high", "critical"] },
          "status": { "type": "string" },
          "alert_type": { "type": "string" }
        }
      },
      "FocusRow": {
        "type": "object",
        "required": ["ChargePeriodStart", "BilledCost", "ServiceName", "ChargeCategory"],
        "properties": {
          "ChargePeriodStart": { "type": "string", "description": "ISO 8601 or YYYY-MM-DD." },
          "ChargePeriodEnd": { "type": "string", "description": "Exclusive end. Omit for single-period rows." },
          "BilledCost": { "type": "number", "description": "Gross charge. Negative for credits/refunds." },
          "EffectiveCost": { "type": "number", "description": "Net/amortized cost used by analytics. Defaults to BilledCost if omitted." },
          "ListCost": { "type": "number" },
          "BillingCurrency": { "type": "string", "description": "ISO 4217. Defaults to the provider's default currency." },
          "ChargeCategory": { "type": "string", "enum": ["Usage", "Purchase", "Tax", "Credit", "Adjustment", "Discount", "Fee", "Refund"], "description": "Canonical FOCUS values; Vantage-style Discount/Fee/Refund are accepted and folded in." },
          "ServiceName": { "type": "string" },
          "ServiceCategory": { "type": "string" },
          "ResourceId": { "type": "string" },
          "RegionId": { "type": "string" },
          "BillingAccountId": { "type": "string" },
          "SubAccountId": { "type": "string" },
          "ConsumedQuantity": { "type": "number" },
          "ConsumedUnit": { "type": "string" },
          "Tags": { "type": "object", "additionalProperties": { "type": "string" } },
          "x_stackspend_model": { "type": "string", "description": "Profile v1.1 extension. Model name, surfaced in the AI Explorer." },
          "x_stackspend_tokens_input": { "type": "number", "description": "Profile v1.1 extension. Input tokens; any token column marks the row token-denominated." },
          "x_stackspend_tokens_output": { "type": "number", "description": "Profile v1.1 extension. Output tokens." },
          "x_stackspend_tokens_cache_read": { "type": "number", "description": "Profile v1.1 extension. Cache-read tokens." },
          "x_stackspend_tokens_cache_creation": { "type": "number", "description": "Profile v1.1 extension. Cache-creation tokens." }
        },
        "additionalProperties": true
      },
      "FocusIngestBody": {
        "type": "object",
        "required": ["rows"],
        "properties": {
          "format": { "type": "string", "const": "stackspend.focus" },
          "version": { "type": "string", "enum": ["1", "1.1"], "description": "Profile version. 1.1 adds the optional x_stackspend_* model/token extension columns." },
          "rows": { "type": "array", "minItems": 1, "maxItems": 10000, "items": { "$ref": "#/components/schemas/FocusRow" } }
        }
      }
    },
    "responses": {
      "IngestAccepted": {
        "description": "Accepted for processing.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "data": {
                  "type": "object",
                  "properties": {
                    "accepted": { "type": "boolean" },
                    "batch_id": { "type": "string" },
                    "row_count": { "type": "integer" },
                    "cost_line_item_count": { "type": "integer" }
                  }
                }
              }
            }
          }
        }
      },
      "ValidationError": { "description": "Invalid request (VALIDATION_ERROR).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unauthorized": { "description": "Missing or invalid API key (UNAUTHORIZED).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Forbidden": { "description": "Key is valid but not permitted: it lacks the required scope (INVALID_SCOPE), the workspace is not on a plan that includes API access (PLAN_RESTRICTED), or the ingest key is scoped to a different provider connection (FORBIDDEN).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound": { "description": "Resource or connection not found (NOT_FOUND).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    }
  }
}
