Skip to content

API reference

CivicFlow API

A predictable REST API and signed webhooks for incidents, teams, categories and residents — the same API our own apps are built on.

Base URL https://api.civicflow.site/v2 v2 · Stable OpenAPI 3.1
On this page

Overview

The API is organized around resources with predictable URLs. Requests and responses are JSON over HTTPS, timestamps are ISO 8601 in UTC, and every list endpoint uses the same cursor pagination.

Each workspace has a production and a sandbox environment with separate API keys. Sandbox data is never shown to residents.

Protocol
REST over HTTPS (TLS 1.3)
Format
JSON, UTF-8
Specification
OpenAPI 3.1
Events
Signed webhooks

openapi.yaml is available in your workspace under Settings → Developers.

Authentication

Authenticate with a secret API key sent as a Bearer token. Admins create keys under Settings → Developers; each key has a name, an environment and a set of scopes, and can be rotated or revoked at any time.

Shell Authorization header
curl https://api.civicflow.site/v2/incidents \  -H "Authorization: Bearer $CIVICFLOW_KEY"
Scopes
Scope Grants
incidents:read Read incidents, history and attachments metadata
incidents:write Create, update, assign and resolve incidents
comments:write Post internal notes and replies to residents
attachments:write Upload and delete attachments
teams:read Read teams, members and service areas
categories:write Create and update categories and SLA policies
webhooks:manage Create, test and delete webhook endpoints
residents:read Read and export resident profiles (personal data)
residents:erase Anonymize residents to fulfil erasure requests

Rate limits

Limits apply per workspace and environment, with a short burst allowance on top. Every response includes your current budget; when you exceed it, the API returns 429 with a Retry-After header.

Limits by plan
Plan Requests / minute Burst Access
Starter6010Read-only endpoints
Growth600100Read and write
Enterprise from 3,000 CustomRead and write, custom limits

Response headers

X-RateLimit-Limit
Requests allowed in the current window
X-RateLimit-Remaining
Requests left in the current window
X-RateLimit-Reset
Unix time when the window resets
Retry-After
Seconds to wait before retrying (on 429 only)

Pagination

List endpoints return up to limit items (default 25, maximum 100) and a next_cursor. Pass it as cursor to fetch the next page; when has_more is false, you have reached the end. Cursors are stable while you page, even as new incidents arrive.

Shell GET /v2/incidents
# First pagecurl "https://api.civicflow.site/v2/incidents?limit=50" \  -H "Authorization: Bearer $CIVICFLOW_KEY"# Next page: pass next_cursor from the previous responsecurl "https://api.civicflow.site/v2/incidents?limit=50&cursor=c_8f3a91d2" \  -H "Authorization: Bearer $CIVICFLOW_KEY"

Errors

CivicFlow uses conventional HTTP status codes. Error bodies always include a machine-readable code, a human-readable message and a request_id to quote when you contact support.

Error codes
Status Code Meaning
400 invalid_request The request is malformed — e.g. invalid JSON or an unknown parameter.
401 unauthenticated The API key is missing, revoked or invalid.
403 insufficient_scope The key is valid but lacks the scope for this action.
404 not_found The resource does not exist in this workspace.
409 conflict The request conflicts with the current state — e.g. resolving a resolved incident.
422 validation_failed Validation failed; see the field errors in details.
429 rate_limited Rate limit exceeded; retry after the Retry-After interval.
500 internal_error Something went wrong on our side. Safe to retry with backoff.
JSON 422 Unprocessable Entity
{  "error": {    "code": "validation_failed",    "message": "location is required for category lighting",    "details": [{ "field": "location", "issue": "missing" }],    "request_id": "req_7c41e09b2a"  }}

Versioning & deprecation

The major version is part of the URL (/v2). Within a version we only make additive changes: new endpoints, new optional parameters, new fields and new event types. Build clients that ignore unknown fields.

  • Breaking changes ship only in a new major version.
  • Deprecated endpoints keep working for at least 12 months and return Deprecation and Sunset headers.
  • Admins are notified by email and in the workspace, and see deprecated calls in the API usage report.
  • API v1 is deprecated and will be retired on 31 March 2027.

Endpoints

Every resource follows the same patterns. The incidents list is documented in full below; the rest is in openapi.yaml.

Incidents

  • GET /v2/incidents List incidents, filtered and sorted Documented below
  • POST /v2/incidents Create an incident on behalf of a resident or system
  • GET /v2/incidents/{id} Retrieve an incident with its full history
  • PATCH /v2/incidents/{id} Update status, priority, category or location
  • POST /v2/incidents/{id}/assign Assign an incident to a team or person
  • POST /v2/incidents/{id}/resolve Resolve an incident with a note and proof photos

Comments

  • GET /v2/incidents/{id}/comments List internal notes and resident replies
  • POST /v2/incidents/{id}/comments Add an internal note or reply to the resident
  • DELETE /v2/comments/{id} Delete an internal note

Attachments

  • GET /v2/incidents/{id}/attachments List photos and files on an incident
  • POST /v2/incidents/{id}/attachments Upload a photo or file (up to 25 MB)
  • DELETE /v2/attachments/{id} Delete an attachment

Teams

  • GET /v2/teams List teams and field crews
  • GET /v2/teams/{id} Retrieve a team with members and service area
  • PATCH /v2/teams/{id} Update a team’s members, shifts or service area

Categories

  • GET /v2/categories List categories with their SLA policies
  • POST /v2/categories Create a category
  • PATCH /v2/categories/{key} Update a category or its SLA policy

Webhooks

  • GET /v2/webhooks List webhook endpoints
  • POST /v2/webhooks Register a webhook endpoint for selected events
  • POST /v2/webhooks/{id}/test Send a signed test event to an endpoint
  • DELETE /v2/webhooks/{id} Delete a webhook endpoint

Residents

  • GET /v2/residents/{id} Retrieve a resident profile
  • GET /v2/residents/{id}/export Export everything held about a resident (access request)
  • POST /v2/residents/{id}/erase Anonymize a resident (erasure request)

List incidents

GET /v2/incidents

Returns incidents in the workspace, newest first. Requires the incidents:read scope. Combine filters freely — they are joined with AND.

Query parameters

Query parameters for GET /v2/incidents
Parameter Type Description
status stringFilter by status: open, urgent, assigned or resolved. Comma-separated for several.
category stringCategory key, e.g. lighting.
district stringDistrict key, e.g. north.
team stringOnly incidents assigned to this team id.
updated_since timestampOnly incidents updated at or after this ISO 8601 timestamp.
sort stringcreated_at, -created_at (default), priority or sla_due_at.
limit integerItems per page, 1–100. Defaults to 25.
cursor stringThe next_cursor from a previous page.

Example request

GET /v2/incidents
curl "https://api.civicflow.site/v2/incidents?status=open,urgent&district=north&limit=2" \  -H "Authorization: Bearer $CIVICFLOW_KEY"

Example response

JSON 200 OK
{  "data": [    {      "id": "CF-4821",      "title": "Streetlight outage",      "category": "lighting",      "status": "urgent",      "priority": "high",      "district": "north",      "location": { "address": "Linden St & 4th Ave", "lat": 52.3791, "lng": 4.8994 },      "assigned_team": "elec-4",      "created_at": "2026-10-08T09:12:44Z",      "sla_due_at": "2026-10-08T21:12:00Z"    },    {      "id": "CF-4833",      "title": "Hydrant leak",      "category": "water",      "status": "urgent",      "priority": "high",      "district": "north",      "location": { "address": "Birch Ln", "lat": 52.3804, "lng": 4.9051 },      "assigned_team": null,      "created_at": "2026-10-08T08:47:10Z",      "sla_due_at": "2026-10-08T12:47:10Z"    }  ],  "has_more": true,  "next_cursor": "c_8f3a91d2"}

Webhooks

Register an HTTPS endpoint and choose the events it receives. Deliveries are JSON POST requests; respond with any 2xx within 10 seconds. Failed deliveries are retried with exponential backoff for up to 24 hours, and every attempt is visible in the delivery log.

Webhook events
Event Sent when
incident.created A new incident is reported by a resident, staff member or integration.
incident.assigned An incident is assigned or reassigned to a team or person.
incident.status_changed An incident’s status changes. Includes previous_status.
incident.resolved An incident is resolved, with resolution note and proof photos.
comment.created An internal note or resident reply is added.

Verify signatures

Every delivery carries a CivicFlow-Signature header with a timestamp and an HMAC-SHA256 of timestamp.body, signed with the endpoint’s secret. Verify it against the raw request body and reject deliveries older than five minutes.

JavaScript verify-signature.js
import crypto from "node:crypto";// header: "t=1791460023,v1=5f2b…"; rawBody: the unparsed request bodyexport function verifySignature(header, rawBody, secret) {  const { t, v1 } = Object.fromEntries(header.split(",").map((part) => part.split("=")));  const expected = crypto    .createHmac("sha256", secret)    .update(`${t}.${rawBody}`)    .digest("hex");  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;  return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));}

SDKs

Official, typed clients that handle authentication, pagination and retries for you.

  • Node.js

    3.4.0

    TypeScript types for every resource. Node 18 and later.

    npm install @civicflow/node
  • Python

    1.2.1

    Sync and async clients with type hints. Python 3.9 and later.

    pip install civicflow
  • Go

    Coming soon

    An idiomatic Go module is in private beta with two customers.

Changelog

  1. 1 October 2026

    v2.8

    Incremental sync for incidents

    Added the updated_since filter to GET /v2/incidents, and previous_status to incident.status_changed events.

  2. 9 September 2026

    v2.7

    Larger attachments

    Attachments can now be up to 25 MB (previously 10 MB). Uploads over 5 MB use resumable upload sessions.

  3. 12 August 2026

    v2.6

    Residents API and Python SDK 1.0

    New endpoints to export and anonymize residents for access and erasure requests. The Python SDK reached 1.0.

Building an integration?

Read the guides for setup and concepts, check live API availability, or review how we protect your data.