{
  "openapi": "3.1.0",
  "info": {
    "title": "Lossline API",
    "version": "1.0.0",
    "description": "Watch supported fire-department areas and receive one automatically enriched record per unique incident."
  },
  "servers": [
    { "url": "https://api.lossline.app", "description": "Production" }
  ],
  "security": [{ "bearerAuth": [] }],
  "paths": {
    "/v1/me": {
      "get": {
        "summary": "Get the current organization",
        "operationId": "getOrganization",
        "responses": { "200": { "$ref": "#/components/responses/Organization" } }
      }
    },
    "/v1/areas": {
      "get": {
        "summary": "List supported areas",
        "operationId": "listAreas",
        "parameters": [
          { "$ref": "#/components/parameters/Query" },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": {
            "description": "Supported fire-department areas",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/Area" } },
                    "page": { "type": "integer" },
                    "limit": { "type": "integer" }
                  },
                  "required": ["data", "page", "limit"]
                }
              }
            }
          }
        }
      }
    },
    "/v1/watches": {
      "get": {
        "summary": "List watched areas",
        "operationId": "listWatches",
        "responses": { "200": { "description": "Current watched areas" } }
      },
      "post": {
        "summary": "Watch areas",
        "operationId": "createWatches",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "area_ids": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 500,
                    "items": { "type": "string", "example": "area_MDFR" }
                  }
                },
                "required": ["area_ids"]
              }
            }
          }
        },
        "responses": { "201": { "description": "Areas are now watched" } }
      }
    },
    "/v1/watches/{area_id}": {
      "delete": {
        "summary": "Stop watching an area",
        "operationId": "deleteWatch",
        "parameters": [{ "$ref": "#/components/parameters/AreaId" }],
        "responses": { "204": { "description": "Watch removed" } }
      }
    },
    "/v1/customers": {
      "get": {
        "summary": "List platform customers",
        "description": "Returns the opaque customer IDs and area assignments supplied by your platform.",
        "operationId": "listCustomers",
        "parameters": [
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": { "200": { "description": "Platform customers" } }
      }
    },
    "/v1/customers/{customer_id}": {
      "get": {
        "summary": "Get a platform customer",
        "operationId": "getCustomer",
        "parameters": [{ "$ref": "#/components/parameters/CustomerId" }],
        "responses": { "200": { "description": "Customer and watched areas" } }
      },
      "put": {
        "summary": "Create or replace a platform customer's area subscriptions",
        "description": "Idempotently upserts the opaque customer ID and replaces its complete area list.",
        "operationId": "putCustomer",
        "parameters": [{ "$ref": "#/components/parameters/CustomerId" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "area_ids": {
                    "type": "array",
                    "maxItems": 500,
                    "items": { "type": "string", "example": "area_MDFR" }
                  },
                  "metadata": {
                    "type": "object",
                    "description": "Optional non-sensitive routing metadata up to 4 KB."
                  }
                },
                "required": ["area_ids"]
              }
            }
          }
        },
        "responses": { "200": { "description": "Customer subscriptions replaced" } }
      },
      "delete": {
        "summary": "Deactivate a platform customer",
        "operationId": "deactivateCustomer",
        "parameters": [{ "$ref": "#/components/parameters/CustomerId" }],
        "responses": { "204": { "description": "Customer deactivated and future matching stopped" } }
      }
    },
    "/v1/customers/{customer_id}/incidents": {
      "get": {
        "summary": "List incidents matched to one platform customer",
        "description": "Useful for onboarding backfill and support. It does not create another charge or webhook delivery.",
        "operationId": "listCustomerIncidents",
        "parameters": [
          { "$ref": "#/components/parameters/CustomerId" },
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": { "200": { "description": "Finalized records matched to this customer" } }
      }
    },
    "/v1/incidents": {
      "get": {
        "summary": "List finalized incident records",
        "operationId": "listIncidents",
        "parameters": [
          { "$ref": "#/components/parameters/Page" },
          { "$ref": "#/components/parameters/Limit" }
        ],
        "responses": {
          "200": {
            "description": "Finalized records already billed to the organization",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/CompletedIncident" } },
                    "page": { "type": "integer" },
                    "limit": { "type": "integer" }
                  },
                  "required": ["data", "page", "limit"]
                }
              }
            }
          }
        }
      }
    },
    "/v1/incidents/{incident_id}": {
      "get": {
        "summary": "Get a finalized incident record",
        "operationId": "getIncident",
        "parameters": [{ "$ref": "#/components/parameters/IncidentId" }],
        "responses": {
          "200": {
            "description": "Completed incident",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CompletedIncident" } } }
          },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/usage": {
      "get": {
        "summary": "Get balance and usage ledger",
        "operationId": "getUsage",
        "responses": { "200": { "description": "Current balance and immutable ledger entries" } }
      }
    },
    "/v1/webhook-endpoint": {
      "get": {
        "summary": "Get the webhook endpoint",
        "operationId": "getWebhookEndpoint",
        "responses": { "200": { "description": "Webhook endpoint without its signing secret" } }
      },
      "put": {
        "summary": "Create or replace the webhook endpoint",
        "description": "Rotates the signing secret. The new secret is returned once.",
        "operationId": "putWebhookEndpoint",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": { "url": { "type": "string", "format": "uri", "example": "https://example.com/lossline" } },
                "required": ["url"]
              }
            }
          }
        },
        "responses": { "200": { "description": "Endpoint and one-time signing secret" } }
      },
      "delete": {
        "summary": "Disable webhook delivery",
        "operationId": "deleteWebhookEndpoint",
        "responses": { "204": { "description": "Endpoint disabled" } }
      }
    },
    "/v1/keys": {
      "get": {
        "summary": "List API keys",
        "operationId": "listApiKeys",
        "responses": { "200": { "description": "Key metadata; secret values are never returned" } }
      },
      "post": {
        "summary": "Create an API key",
        "operationId": "createApiKey",
        "requestBody": {
          "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string" } } } } }
        },
        "responses": { "201": { "description": "One-time API key response" } }
      }
    },
    "/v1/keys/{key_id}": {
      "delete": {
        "summary": "Revoke an API key",
        "operationId": "revokeApiKey",
        "parameters": [{ "name": "key_id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": { "204": { "description": "Key revoked" } }
      }
    }
  },
  "webhooks": {
    "incidentCompleted": {
      "post": {
        "summary": "A unique incident finished automatic enrichment",
        "parameters": [
          { "name": "Lossline-Id", "in": "header", "required": true, "schema": { "type": "string" } },
          { "name": "Lossline-Timestamp", "in": "header", "required": true, "schema": { "type": "string" } },
          { "name": "Lossline-Signature", "in": "header", "required": true, "schema": { "type": "string" } }
        ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CompletedIncident" } } }
        },
        "responses": { "200": { "description": "Return any 2xx response to acknowledge delivery" } }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "Lossline API key" }
    },
    "parameters": {
      "Query": { "name": "query", "in": "query", "schema": { "type": "string", "maxLength": 80 } },
      "Page": { "name": "page", "in": "query", "schema": { "type": "integer", "minimum": 1, "default": 1 } },
      "Limit": { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 } },
      "AreaId": { "name": "area_id", "in": "path", "required": true, "schema": { "type": "string", "example": "area_MDFR" } },
      "CustomerId": {
        "name": "customer_id",
        "in": "path",
        "required": true,
        "description": "Your own opaque customer identifier.",
        "schema": { "type": "string", "pattern": "^[A-Za-z0-9._:-]{1,128}$", "example": "customer_8421" }
      },
      "IncidentId": { "name": "incident_id", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^inc_" } }
    },
    "responses": {
      "Organization": { "description": "Current API organization" },
      "NotFound": { "description": "The resource was not found or was not purchased by this organization" }
    },
    "schemas": {
      "Area": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "short_name": { "type": "string" },
          "location": { "type": "string" },
          "latitude": { "type": ["number", "null"] },
          "longitude": { "type": ["number", "null"] },
          "timezone": { "type": ["string", "null"] },
          "boundary": { "type": ["array", "null"] }
        },
        "required": ["id", "name"]
      },
      "CompletedIncident": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "pattern": "^evt_" },
          "type": { "const": "incident.finalized" },
          "schema_version": { "const": "1.0" },
          "created_at": { "type": "string", "format": "date-time" },
          "matching_customer_ids": {
            "type": "array",
            "description": "Every active platform customer whose area subscription matched this incident when it was queued.",
            "items": { "type": "string" }
          },
          "incident": { "type": "object", "additionalProperties": true },
          "enrichment": {
            "type": "object",
            "properties": {
              "status": { "type": "string", "enum": ["matched", "no_match", "unavailable"] },
              "owner_name": { "type": ["string", "null"] },
              "owner_type": { "type": ["string", "null"] },
              "phones": { "type": "array", "items": { "type": "string" } },
              "emails": { "type": "array", "items": { "type": "string" } },
              "mailing_address": { "type": ["string", "null"] },
              "property": { "type": "object" }
            },
            "required": ["status", "phones", "emails", "property"]
          },
          "billing": {
            "type": "object",
            "properties": {
              "amount_cents": { "type": "integer", "enum": [75, 100] },
              "amount": { "type": "number", "enum": [0.75, 1] },
              "currency": { "const": "usd" }
            },
            "required": ["amount_cents", "amount", "currency"]
          }
        },
        "required": ["id", "type", "schema_version", "created_at", "matching_customer_ids", "incident", "enrichment", "billing"]
      }
    }
  }
}
