{
  "openapi": "3.1.0",
  "info": {
    "title": "ZiplyHuman VoiceOps API",
    "version": "1.0.0",
    "description": "REST API for multi-tenant Voice AI observability. All routes require tenant-scoped authentication unless noted. Pagination: cursor or page/limit. Idempotency-Key header supported on writes."
  },
  "servers": [
    { "url": "https://human.ziply.my/api/v1", "description": "Production" },
    { "url": "http://localhost:3456/api/v1", "description": "Local" }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API key"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean", "example": false },
          "error": { "type": "string" },
          "code": { "type": "string", "example": "FORBIDDEN" }
        }
      },
      "Call": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "agentId": { "type": "string" },
          "status": { "type": "string", "enum": ["completed", "failed", "in_progress", "abandoned", "escalated"] },
          "startedAt": { "type": "string", "format": "date-time" },
          "durationMs": { "type": "integer" },
          "qualityScore": { "type": "number" },
          "latency": {
            "type": "object",
            "properties": {
              "asrMs": { "type": "integer" },
              "llmMs": { "type": "integer" },
              "ttsMs": { "type": "integer" },
              "totalMs": { "type": "integer" }
            }
          }
        }
      },
      "Outcome": {
        "type": "object",
        "required": ["callId", "type"],
        "properties": {
          "id": { "type": "string" },
          "callId": { "type": "string" },
          "agent": { "type": "string" },
          "type": { "type": "string" },
          "value": { "type": "string" },
          "source": { "type": "string" },
          "revenueUsd": { "type": "number" }
        }
      },
      "Health": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "service": { "type": "string" },
          "database": {
            "type": "object",
            "properties": {
              "connected": { "type": "boolean" },
              "latencyMs": { "type": "integer" }
            }
          }
        }
      }
    }
  },
  "security": [{ "bearerAuth": [] }],
  "paths": {
    "/health": {
      "get": {
        "summary": "Health check",
        "security": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Health" },
                "example": {
                  "ok": true,
                  "service": "ziplyhuman-voiceops",
                  "database": { "connected": true, "latencyMs": 1 }
                }
              }
            }
          },
          "503": { "description": "Degraded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/realtime": {
      "get": {
        "summary": "Live metrics snapshot",
        "responses": {
          "200": {
            "description": "Live KPIs",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "live": {
                    "concurrentCalls": 247,
                    "successRate": 86.2,
                    "p95LatencyMs": 1900
                  }
                }
              }
            }
          }
        }
      }
    },
    "/outcomes": {
      "get": {
        "summary": "List outcomes",
        "parameters": [
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 100 } },
          { "name": "callId", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Outcome list",
            "content": {
              "application/json": {
                "example": { "ok": true, "source": "postgres", "items": [] }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Upsert outcome",
        "parameters": [
          { "name": "Idempotency-Key", "in": "header", "schema": { "type": "string" } }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/Outcome" },
              "example": {
                "callId": "call_8f2a91",
                "type": "CSAT",
                "value": "5/5",
                "source": "post-call SMS"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "example": { "ok": true, "id": "oc_123" }
              }
            }
          },
          "403": { "description": "Forbidden" },
          "429": { "description": "Rate limited" }
        }
      }
    },
    "/webhooks/events": {
      "post": {
        "summary": "Ingest provider webhook event",
        "description": "HMAC-signed, timestamp-validated, idempotent ingestion.",
        "parameters": [
          { "name": "X-Signature", "in": "header", "schema": { "type": "string" } },
          { "name": "X-Timestamp", "in": "header", "schema": { "type": "string" } },
          { "name": "Idempotency-Key", "in": "header", "schema": { "type": "string" } }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "type": "call.completed",
                "agent_id": "agt_01",
                "provider_call_id": "ext_99",
                "latency_ms": 980
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Accepted" },
          "401": { "description": "Invalid signature" },
          "409": { "description": "Duplicate idempotency key" }
        }
      }
    },
    "/calls": {
      "get": {
        "summary": "List calls",
        "description": "Filter, sort, paginate. RLS enforced.",
        "parameters": [
          { "name": "status", "in": "query", "schema": { "type": "string" } },
          { "name": "agentId", "in": "query", "schema": { "type": "string" } },
          { "name": "from", "in": "query", "schema": { "type": "string", "format": "date-time" } },
          { "name": "to", "in": "query", "schema": { "type": "string", "format": "date-time" } },
          { "name": "cursor", "in": "query", "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50 } },
          { "name": "sort", "in": "query", "schema": { "type": "string", "example": "-startedAt" } }
        ],
        "responses": {
          "200": {
            "description": "Paginated calls",
            "content": {
              "application/json": {
                "example": {
                  "ok": true,
                  "items": [],
                  "nextCursor": null
                }
              }
            }
          }
        }
      }
    },
    "/calls/{id}": {
      "get": {
        "summary": "Get call detail",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Call with transcript/trace refs",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Call" }
              }
            }
          },
          "404": { "description": "Not found" }
        }
      }
    },
    "/incidents": {
      "get": { "summary": "List incidents", "responses": { "200": { "description": "OK" } } },
      "post": { "summary": "Create incident", "responses": { "201": { "description": "Created" } } }
    },
    "/alerts": {
      "get": { "summary": "List alerts", "responses": { "200": { "description": "OK" } } }
    },
    "/deployments": {
      "get": { "summary": "List deployments", "responses": { "200": { "description": "OK" } } }
    },
    "/metrics": {
      "get": {
        "summary": "Query metrics",
        "parameters": [
          { "name": "name", "in": "query", "required": true, "schema": { "type": "string" } },
          { "name": "from", "in": "query", "schema": { "type": "string" } },
          { "name": "to", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": { "200": { "description": "Time series" } }
      }
    },
    "/usage": {
      "get": { "summary": "Usage meters", "responses": { "200": { "description": "OK" } } }
    },
    "/billing/invoices": {
      "get": { "summary": "Invoice history", "responses": { "200": { "description": "OK" } } }
    },
    "/audit": {
      "get": { "summary": "Audit log", "responses": { "200": { "description": "OK" } } }
    }
  },
  "x-rate-limits": {
    "default": "600 req/min per API key",
    "ingest": "5000 events/min per org"
  },
  "x-error-codes": [
    "UNAUTHORIZED",
    "FORBIDDEN",
    "NOT_FOUND",
    "VALIDATION_ERROR",
    "IDEMPOTENCY_CONFLICT",
    "RATE_LIMITED",
    "PROVIDER_ERROR"
  ]
}
