https://api.lossline.appLossline API
Build fire intelligence into your product.
Watch supported fire-department areas, route incidents to your own customer IDs, and receive one finalized record after automatic owner and property enrichment.
Keep Lossline API keys on your backend. Do not embed them in browser code, mobile apps, or public repositories.
Get started
Quickstart
A production integration has four parts: create a key, choose areas, assign those areas to your customer IDs, and register one webhook endpoint.
- 01Create an organization and API keyUse the Developer Console. Secret values are displayed once.
- 02Find supported areasSearch the complete department catalog and store stable
area_IDs. - 03Configure routingUse organization watches or replace each platform customer’s full area list.
- 04Receive finalized incidentsVerify the signature, acknowledge with any 2xx response, then route by
matching_customer_ids.
1. Find an area
All API requests use the production base URL and a Bearer token.
curl "https://api.lossline.app/v1/areas?query=miami&limit=10" \
-H "Authorization: Bearer $LOSSLINE_API_KEY"{
"data": [
{
"id": "area_MDFR",
"name": "Miami-Dade Fire Rescue",
"short_name": "Miami-Dade",
"location": "Miami-Dade County, Florida",
"latitude": 25.7617,
"longitude": -80.1918,
"timezone": "America/New_York",
"boundary": [/* GeoJSON coordinates */]
}
],
"page": 1,
"limit": 10
}2. Add a platform customer
This request is idempotent. It creates the customer or replaces the customer’s complete area list.
curl -X PUT "https://api.lossline.app/v1/customers/customer_8421" \
-H "Authorization: Bearer $LOSSLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"area_ids": ["area_MDFR", "area_FL_BSO"],
"metadata": { "plan": "pro" }
}'{
"data": {
"id": "customer_8421",
"status": "active",
"area_ids": ["area_FL_BSO", "area_MDFR"],
"backfill_queued": 4
}
}3. Register your webhook
The response includes a new whsec_ signing secret exactly once. Store it in your secret manager.
curl -X PUT "https://api.lossline.app/v1/webhook-endpoint" \
-H "Authorization: Bearer $LOSSLINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://api.example.com/webhooks/lossline" }'Foundations
Authentication
Authenticate every request with an API key in the Authorization header.
Authorization: Bearer ll_live_••••••••••••••••Live keys
Keys begin with ll_live_. A key belongs to one organization and inherits that organization’s access.
One-time display
The full key is returned only when it is created. Lossline stores a one-way hash and cannot recover it later.
Rotation
Create the replacement, deploy it, verify its last-used timestamp, then revoke the previous key.
Key limits
Each organization may keep up to 10 active keys. The key authenticating a request cannot revoke itself.
Core concepts
How processing works
Lossline turns many source updates into one stable, organization-scoped incident record.
Areas and watches
An area represents one supported department feed. Its stable ID begins with area_.
Organization watches
Use POST /v1/watches when every incident in an area should belong to the organization, even without platform customer routing.
Customer watches
Use customer area lists when one platform account serves many end customers and needs recipient IDs returned in each incident.
Shared collection
An upstream area is collected once even when many Lossline organizations and customers watch it.
Boundaries
Area responses can include coordinates and geographic boundary data for discovery and map interfaces.
Platform customers
Use your own opaque identifier. Lossline stores the routing relationship, not your customer’s name, email, or phone number.
customer_8421Up to 128 characters: letters, numbers, period, underscore, colon, or hyphen.- Replace, do not append. Each PUT replaces the complete area list so your system can safely replay its current state.
- Metadata is optional. Store up to 4 KB of non-sensitive routing metadata.
- Delivery is consolidated. One incident includes all matching customer IDs in one array.
- Deactivation is safe. DELETE stops future matching without deleting already finalized incident history.
Incidents and billing
Each unique incident is processed and charged once per organization, even when multiple watched areas or customer subscriptions overlap.
A $1.00 reservation is placed when processing starts. If the final result is not a verified match, $0.25 is returned automatically. Repeated reads, webhook retries, later source updates, and additional matching customers do not create another charge.
Backfill
Adding an organization watch or customer area list automatically scans the previous 24 hours.
- Existing incident records already purchased by the organization are attached to newly matching customer IDs without another charge.
- New historical records are processed newest first while the organization has enough balance.
- Customer-scoped history is available from
GET /v1/customers/{customer_id}/incidents. - Reading historical records never triggers another webhook or charge.
Webhooks
Delivery lifecycle
Lossline sends one POST request for each finalized incident to the organization’s active HTTPS endpoint.
Lossline-IdStable delivery ID beginning with dlv_. Use it for deduplication.
Lossline-TimestampUnix timestamp in seconds for replay protection.
Lossline-SignatureVersioned Base64 signature in the form v1,signature.
User-AgentLossline-Webhooks/1.0
Verify webhook signatures
Use the exact raw request body. Parsing and re-serializing JSON changes the signed bytes.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyLosslineWebhook(req, rawBody, secret) {
const deliveryId = req.headers["lossline-id"];
const timestamp = req.headers["lossline-timestamp"];
const received = req.headers["lossline-signature"]?.replace(/^v1,/, "");
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!deliveryId || !timestamp || !received || age > 300) return false;
const key = secret.replace(/^whsec_/, "");
const expected = createHmac("sha256", key)
.update(deliveryId + "." + timestamp + "." + rawBody)
.digest("base64");
const a = Buffer.from(received);
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}Reject timestamps more than five minutes from your server time and ignore delivery IDs you already processed.
Incident payload
The payload is frozen when billing settles. REST reads and every webhook retry return the same record.
{
"id": "evt_7d9c8b0e-3f6e-4af3-96c1-230ccfb56ac7",
"type": "incident.finalized",
"schema_version": "1.0",
"created_at": "2026-08-17T18:42:11.930Z",
"matching_customer_ids": ["customer_8421", "customer_9107"],
"incident": {
"id": "inc_e12661f9-a182-447f-b8f8-d73c54498a15",
"event_type": "Structure Fire",
"severity": "confirmed",
"status": "active",
"title": "Residential structure fire",
"department": {
"id": "area_MDFR",
"name": "Miami-Dade Fire Rescue"
},
"location": {
"address": "1400 Collins Avenue",
"city": "Miami Beach",
"state": "FL",
"postal_code": "33139",
"latitude": 25.7865,
"longitude": -80.1301
},
"dispatch": {
"reported_at": "2026-08-17T18:39:02.000Z",
"last_updated_at": "2026-08-17T18:41:44.000Z",
"closed_at": null,
"units": ["E1", "E2", "L1"],
"summary": "3 units responding"
}
},
"enrichment": {
"status": "matched",
"owner_name": "Collins 1400 Holdings LLC",
"owner_type": "business",
"phones": ["+13055550136"],
"emails": ["office@example.com"],
"mailing_address": "1400 Collins Avenue, Miami Beach, FL 33139",
"property": {
"estimated_value": 6200000,
"year_built": 1956,
"last_sold_year": 2021
}
},
"billing": {
"amount_cents": 100,
"amount": 1,
"currency": "usd"
}
}Retries and acknowledgements
Return any 2xx status within 15 seconds. Redirects are not followed.
After eight unsuccessful attempts, the delivery is marked failed. Delivery state and attempt history remain visible in the Developer Console.
API reference
Every v1 endpoint.
All request and response bodies use JSON unless the endpoint returns 204 No Content.
Organization
/v1/me200Retrieve the current organization
Returns the organization attached to the API key, including status, balance, and currency.
Areas
/v1/areas200List supported areas
Search enabled, discoverable department areas in alphabetical order.
- query: optional search string up to 80 characters
- page: integer from 1 to 10,000
- limit: 1 to 100, default 50
Organization watches
/v1/watches200List watched areas
Returns every organization-wide area watch with its creation time and area details.
/v1/watches201Watch areas
Adds one to 500 valid area IDs. Existing watches are ignored safely and a 24-hour backfill is queued.
- Body: { area_ids: string[] }
- Response includes watched and backfill_queued
/v1/watches/{area_id}204Stop watching an area
Removes one organization-wide watch. Customer-specific watches are unaffected.
Platform customers
/v1/customers200List customers
Returns opaque IDs, current status, metadata, and complete area assignments.
- page: integer from 1 to 10,000
- limit: 1 to 100, default 50
/v1/customers/{customer_id}200Retrieve a customer
Returns one customer belonging to the authenticated organization.
/v1/customers/{customer_id}200Create or replace a customer
Idempotently upserts the customer and replaces its complete area list.
- area_ids: required array, maximum 500
- metadata: optional object, maximum 4 KB
- Queues a 24-hour backfill
/v1/customers/{customer_id}204Deactivate a customer
Stops future matching and removes current area assignments while preserving prior history.
/v1/customers/{customer_id}/incidents200List customer incidents
Returns finalized records matched to one customer. This read creates no delivery or charge.
- page: integer from 1 to 10,000
- limit: 1 to 100, default 25
Incidents
/v1/incidents200List finalized incidents
Returns organization-scoped records ordered by finalization time, newest first.
- page: integer from 1 to 10,000
- limit: 1 to 100, default 25
/v1/incidents/{incident_id}200Retrieve an incident
Returns one completed incident already purchased by the authenticated organization.
Usage
/v1/usage200Retrieve balance and ledger
Returns the current balance, currency, and immutable reservation, refund, credit, and adjustment entries.
- limit: 1 to 100, default 50
Webhook endpoint
/v1/webhook-endpoint200Retrieve webhook configuration
Returns endpoint metadata without the signing secret.
/v1/webhook-endpoint200Create or replace the endpoint
Requires a public HTTPS URL. Replacing the endpoint rotates the signing secret.
- Body: { url: string }
- The new whsec_ value is displayed once
/v1/webhook-endpoint204Disable delivery
Disables the endpoint without deleting delivery history.
API keys
/v1/keys200List key metadata
Returns names, prefixes, last-used timestamps, expiry, revocation, and creation times. Secret values are never returned.
/v1/keys201Create an API key
Creates a live key and returns the complete secret exactly once.
- Body: { name?: string }
- Maximum 10 active keys per organization
/v1/keys/{key_id}204Revoke an API key
Revokes a key belonging to the organization. A key cannot revoke itself.
Resources
Pagination and request limits
List endpoints return page and limit. Request the next page until data.length is smaller than the requested limit.
Errors
Errors use an HTTP status and a stable machine-readable string.
{
"error": "one_or_more_areas_not_found"
}Inspect the error string and correct the request.
401Invalid or expired API keyCreate a new key or replace the Authorization header.
403Organization is not activeContact the organization owner or Lossline support.
404Resource or area not foundConfirm the ID belongs to your organization.
409Request conflicts with current stateKey limits and current-key revocation use this status.
503A dependent service is unavailableRetry with exponential backoff.

