{
  "openapi": "3.1.0",
  "info": {
    "title": "Tellinex Public API",
    "version": "1.0",
    "description": "Public API for 3rd-party integrations with the Tellinex network operations platform. Provides read access to incidents and SLA credits, write access to incidents (for partner integrations like Calix or ADTRAN), management of webhook subscriptions, and read/feedback access to predictive maintenance work orders.\n\n**Authentication:** Bearer token in `Authorization` header. API keys are issued via the Tellinex admin console and scoped to specific permissions (`incidents.read`, `incidents.write`, `sla.read`, `webhooks.manage`, `work_orders.read`, `work_orders.feedback`). The wildcard scope `*` grants all permissions.\n\n**Rate limiting:** 60 requests per minute per API key, enforced via token-bucket. Exceeded requests return `429` with a `Retry-After` header.\n\n**Webhooks:** All webhook deliveries include a `X-Tellinex-Signature` header in the form `t=<unix>,v1=<hex hmac-sha256>`. The signed payload is `{timestamp}.{request_body}`. Verify signatures using the `signing_secret` returned at webhook creation (shown only once)."
  },
  "servers": [
    { "url": "https://egztpclpcnizcdtfugsv.supabase.co/functions/v1/api-v1", "description": "Production" }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "API Key" }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": { "type": "string", "example": "invalid_type" },
              "message": { "type": "string", "example": "type must be one of: equipment_failure, cable_cut, power_loss, planned_maintenance, congestion, other" }
            }
          }
        }
      },
      "Incident": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "type": { "type": "string", "enum": ["equipment_failure","cable_cut","power_loss","planned_maintenance","congestion","other"] },
          "severity": { "type": "string", "enum": ["nominal","degraded","critical","outage"] },
          "status": { "type": "string", "enum": ["active","planned","resolved_24h","archived"] },
          "title": { "type": "string" },
          "affected_parish": { "type": "string", "nullable": true },
          "first_alarm_at": { "type": "string", "format": "date-time", "nullable": true },
          "created_at": { "type": "string", "format": "date-time" },
          "resolved_at": { "type": "string", "format": "date-time", "nullable": true },
          "estimated_resolution_at": { "type": "string", "format": "date-time", "nullable": true },
          "correlated_alarm_count": { "type": "integer", "nullable": true },
          "isolated_metres": { "type": "number", "nullable": true },
          "predicted_blast_radius": { "type": "object", "nullable": true },
          "predicted_at": { "type": "string", "format": "date-time", "nullable": true }
        }
      },
      "IncidentInput": {
        "type": "object",
        "required": ["type", "title", "city"],
        "properties": {
          "type": { "type": "string", "enum": ["equipment_failure","cable_cut","power_loss","planned_maintenance","congestion","other"] },
          "title": { "type": "string", "maxLength": 500 },
          "city": { "type": "string", "maxLength": 100 },
          "severity": { "type": "string", "enum": ["nominal","degraded","critical","outage"], "default": "degraded" },
          "status": { "type": "string", "enum": ["active","planned","resolved_24h","archived"], "default": "active" },
          "affected_parish": { "type": "string", "nullable": true },
          "affected_postcode_prefix": { "type": "string", "nullable": true },
          "detail": { "type": "string", "maxLength": 2000, "nullable": true },
          "eta": { "type": "string", "maxLength": 200, "nullable": true },
          "map_x": { "type": "number" },
          "map_y": { "type": "number" },
          "root_cause_asset_id": { "type": "string", "format": "uuid", "nullable": true }
        }
      },
      "IncidentPatch": {
        "type": "object",
        "description": "All fields optional. At least one must be provided.",
        "properties": {
          "severity": { "type": "string", "enum": ["nominal","degraded","critical","outage"] },
          "status": { "type": "string", "enum": ["active","planned","resolved_24h","archived"] },
          "title": { "type": "string", "maxLength": 500 },
          "detail": { "type": "string", "maxLength": 2000, "nullable": true },
          "eta": { "type": "string", "maxLength": 200, "nullable": true },
          "estimated_resolution_at": { "type": "string", "format": "date-time", "nullable": true },
          "resolved": { "type": "boolean", "description": "true → sets resolved_at=now(); false → clears resolved_at" }
        }
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "url": { "type": "string", "format": "uri" },
          "event_filter": { "type": "array", "items": { "type": "string" } },
          "status": { "type": "string", "enum": ["active","paused","revoked"] },
          "last_delivery_at": { "type": "string", "format": "date-time", "nullable": true },
          "last_delivery_status": { "type": "integer", "nullable": true },
          "consecutive_failures": { "type": "integer" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "WebhookInput": {
        "type": "object",
        "required": ["url"],
        "properties": {
          "url": { "type": "string", "format": "uri", "description": "Must be https://" },
          "event_filter": {
            "type": "array",
            "items": { "type": "string", "enum": [
              "incident.opened","incident.updated","incident.resolved",
              "work_order.predictive_drafted","work_order.predictive_escalated",
              "work_order.stale_unassigned",
              "sla_credit.applied","work_order.completed",
              "system.health_alert"
            ]},
            "default": ["incident.opened","incident.resolved"]
          }
        }
      },
      "WebhookCreateResponse": {
        "type": "object",
        "properties": {
          "data": {
            "allOf": [
              { "$ref": "#/components/schemas/Webhook" },
              { "type": "object", "properties": { "signing_secret": { "type": "string", "description": "32-byte hex. Save this — it will not be shown again." } } }
            ]
          },
          "notice": { "type": "string" }
        }
      },
      "WorkOrder": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "reference": { "type": "string", "example": "PM-20260428-c0f548" },
          "title": { "type": "string" },
          "description": { "type": "string" },
          "priority": { "type": "string", "enum": ["critical","high","normal","low"] },
          "status": { "type": "string", "enum": ["pending","dispatched","in_progress","evidence_required","validation_pending","validated","rejected","completed","blocked","approved"] },
          "work_type": { "type": "string" },
          "required_role": { "type": "string", "enum": ["fibre_engineer","civils_contractor"] },
          "source_kind": { "type": "string", "example": "predictive_maintenance" },
          "assigned_technician": { "type": "string", "nullable": true },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" },
          "dispatched_at": { "type": "string", "format": "date-time", "nullable": true },
          "started_at": { "type": "string", "format": "date-time", "nullable": true },
          "completed_at": { "type": "string", "format": "date-time", "nullable": true },
          "triggering_asset_id": { "type": "string", "format": "uuid", "nullable": true },
          "triggering_score": { "type": "number", "minimum": 0, "maximum": 1, "nullable": true, "description": "Asset health score at draft time (0=broken, 1=healthy)" },
          "triggering_components": { "type": "object", "description": "Factor breakdown (alarm_density, severity, open_age, reassertion, age) — for ML refinement / debugging" }
        }
      },
      "WorkOrderFeedbackInput": {
        "type": "object",
        "required": ["outcome"],
        "properties": {
          "outcome": {
            "type": "string",
            "enum": ["confirmed_critical","confirmed_warning","completed_proactive","false_positive","superseded","inaccessible"],
            "description": "How the technician resolved the predictive flag. true_positives are confirmed_*/completed_proactive; false_positives are false_positive/superseded; inaccessible means tech could not verify."
          },
          "notes": { "type": "string", "maxLength": 2000, "nullable": true },
          "marked_by": { "type": "string", "maxLength": 200, "nullable": true, "description": "Tech email or name for audit trail" }
        }
      },
      "WorkOrderFeedbackResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "feedback_id": { "type": "string", "format": "uuid" },
              "work_order": { "$ref": "#/components/schemas/WorkOrder" }
            }
          }
        }
      },
      "SlaCredit": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "customer_reference": { "type": "string" },
          "incident_id": { "type": "string", "format": "uuid", "nullable": true },
          "status": { "type": "string" },
          "billable_minutes": { "type": "integer" },
          "credit_amount_usd": { "type": "number" },
          "applied_at": { "type": "string", "format": "date-time", "nullable": true },
          "created_at": { "type": "string", "format": "date-time" }
        }
      }
    }
  },
  "security": [{ "bearerAuth": [] }],
  "paths": {
    "/healthz": {
      "get": {
        "summary": "Health check",
        "security": [],
        "responses": {
          "200": {
            "description": "Service healthy",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" }, "version": { "type": "string" }, "ts": { "type": "string", "format": "date-time" } } } } }
          }
        }
      }
    },
    "/v1/incidents": {
      "get": {
        "summary": "List incidents",
        "description": "Required scope: `incidents.read`",
        "parameters": [
          { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["active","planned","resolved_24h","archived"], "default": "active" } },
          { "name": "parish", "in": "query", "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 } }
        ],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Incident" } }, "count": { "type": "integer" } } } } } },
          "401": { "description": "Auth required" },
          "403": { "description": "Insufficient scope" }
        }
      },
      "post": {
        "summary": "Create incident",
        "description": "Required scope: `incidents.write`. For partner integrations pushing alarm events.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/IncidentInput" } } } },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Incident" } } } } } },
          "400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/v1/incidents/{id}": {
      "get": {
        "summary": "Get incident",
        "description": "Required scope: `incidents.read`",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Incident" } } } } } },
          "404": { "description": "Not found" }
        }
      },
      "patch": {
        "summary": "Update incident",
        "description": "Required scope: `incidents.write`",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/IncidentPatch" } } } },
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Incident" } } } } } },
          "404": { "description": "Not found" }
        }
      }
    },
    "/v1/sla-credits": {
      "get": {
        "summary": "List SLA credits for a customer",
        "description": "Required scope: `sla.read`. If your API key has a `customer_reference` set, you may only query that customer.",
        "parameters": [
          { "name": "customer", "in": "query", "required": true, "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 } }
        ],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/SlaCredit" } }, "count": { "type": "integer" } } } } } },
          "403": { "description": "Forbidden — different customer than key" }
        }
      }
    },
    "/v1/webhooks": {
      "get": {
        "summary": "List my webhook subscriptions",
        "description": "Required scope: `webhooks.manage`",
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Webhook" } }, "count": { "type": "integer" } } } } } }
        }
      },
      "post": {
        "summary": "Create webhook subscription",
        "description": "Required scope: `webhooks.manage`. The `signing_secret` is returned in the response body once and never shown again. Use it to verify the HMAC-SHA256 signature on each delivery.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookInput" } } } },
        "responses": {
          "201": { "description": "Created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookCreateResponse" } } } }
        }
      }
    },
    "/v1/webhooks/{id}": {
      "delete": {
        "summary": "Delete webhook subscription",
        "description": "Required scope: `webhooks.manage`. May only delete webhooks owned by your API key.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "204": { "description": "Deleted" },
          "403": { "description": "Forbidden — different owner" },
          "404": { "description": "Not found" }
        }
      }
    },
    "/v1/work-orders": {
      "get": {
        "summary": "List work orders",
        "description": "Required scope: `work_orders.read`. Returns predictive maintenance work orders by default.",
        "parameters": [
          { "name": "source_kind", "in": "query", "schema": { "type": "string", "default": "predictive_maintenance" } },
          { "name": "status", "in": "query", "schema": { "type": "string" } },
          { "name": "priority", "in": "query", "schema": { "type": "string", "enum": ["critical","high","normal","low"] } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 } }
        ],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/WorkOrder" } }, "count": { "type": "integer" } } } } } }
        }
      }
    },
    "/v1/work-orders/{id}": {
      "get": {
        "summary": "Get work order",
        "description": "Required scope: `work_orders.read`",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/WorkOrder" } } } } } },
          "404": { "description": "Not found" }
        }
      }
    },
    "/v1/work-orders/{id}/feedback": {
      "post": {
        "summary": "Submit predictive maintenance feedback",
        "description": "Required scope: `work_orders.feedback`. Closes a predictive_maintenance work order with a feedback outcome that becomes labelled training data. One feedback per work order — repeated calls return 409.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WorkOrderFeedbackInput" } } } },
        "responses": {
          "201": { "description": "Feedback recorded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WorkOrderFeedbackResponse" } } } },
          "400": { "description": "Invalid outcome or not a predictive WO" },
          "404": { "description": "Work order not found" },
          "409": { "description": "Feedback already submitted" }
        }
      }
    }
  }
}
