Documentation for a production-facing civic intelligence API.
Everything needed to authenticate, scope access, inspect quotas, and call the Civic Sentinel API. Issue keys from the Developer tab and move into integration with a predictable surface.
Introduction
The Civic Sentinel API is a predictable, versioned REST interface. Every request is made over HTTPS, every response is JSON, and every resource is namespaced under a version prefix so your integration never breaks unexpectedly.
Versioned
All endpoints live under /v1 — breaking changes ship as a new version.
Scoped
Keys carry only the permissions you grant them.
Quota-aware
Every response carries your plan and remaining quota — no extra round-trip.
Base URL
Quick start
Go from zero to your first authenticated request in four steps.
Create an account
Sign up and verify your email to unlock the console.
Register an application
Open the Developer tab and create an application to group your keys (Free: 1 app).
Issue a scoped key
Pick only the scopes your integration needs. Copy the public key AND secret — the secret is shown only once.
Exchange for a token
Use the public key and secret once to receive a short-lived JWT bearer token, then call the API with Authorization.
# Exchange key credentials for a bearer token curl https://api.civicsentinel.io/v1/developer-token/ \ -X POST \ -H "Content-Type: application/json" \ -d '{"api_key":"csk_…","api_secret":"css_…"}' # Use response.data.access on API calls curl https://api.civicsentinel.io/v1/profile/ \ -H "Authorization: Bearer $CIVIC_SENTINEL_TOKEN"
Authentication
Integrations exchange a public key + secret pair for a signed, short-lived JWT bearer token. The public key (csk_…) identifies the application; the secret (css_…) proves ownership. API calls then use only the bearer token until it expires.
X-API-Key
The public key shown when you issued the credential. Safe to include in client-side build artefacts only if paired with origin restrictions on your side.
X-API-Secret
The secret shown only once at issue time. Store it in a server-side secret manager — never in source control or client bundles.
# 1. Exchange credentials curl https://api.civicsentinel.io/v1/developer-token/ \ -X POST \ -H "Content-Type: application/json" \ -d '{"api_key":"csk_…","api_secret":"css_…"}' # 2. Call the API with the returned access token curl https://api.civicsentinel.io/v1/alert-destinations/ \ -H "Authorization: Bearer $CIVIC_SENTINEL_TOKEN" \ -H "Content-Type: application/json"
The dashboard also authenticates with JWT bearer tokens (Authorization: Bearer …). Developer tokens carry the application id and scopes so most API requests do not need to re-check the API key secret.
Python SDK
Civic Sentinel for Python
Use your API key and secret in your backend code while the SDK handles token exchange, bearer-token refresh, quota headers, retries after expired tokens, and response envelope parsing.
# Install the SDK pip install civicsentinel # Install with MCP server support pip install "civicsentinel[mcp]"
from civicsentinel import CivicSentinelClient client = CivicSentinelClient( api_key="csk_…", api_secret="css_…", ) countries = client.list_supported_countries() usage = client.get_usage() destinations = client.list_alert_destinations()
Token handling
Exchanges credentials at /v1/developer-token/ and refreshes before expiry.
API helpers
Provides generic get/post/patch/delete plus helpers for usage, countries, destinations, subscriptions, dispatches, and risk assessments.
MCP server
Ships a civicsentinel-mcp command with documentation resources and API tools for agents.
# Run the MCP server
export CIVIC_SENTINEL_API_KEY="csk_…"
export CIVIC_SENTINEL_API_SECRET="css_…"
civicsentinel-mcpAPI keys & scopes
Each key is scoped to the minimum access it needs. Keys can be revoked and rotated at any time without downtime — issue a new key, deploy it, then revoke the old one. Keys belong to applications; per-day API quota is enforced per application, so spreading traffic across more apps does not give you more headroom unless your plan also raises the per-app limit.
profile:readRead the account profile.profile:writeUpdate profile details.destinations:readList alert destinations.destinations:writeCreate or edit destinations.destinations:whatsapp:joinJoin a WhatsApp group destination.subscriptions:readList alert subscriptions.subscriptions:writeCreate or cancel alert subscriptions.dispatches:readRead alert dispatch history.risk_assessments:readRead country risk assessments.billing:readRead billing subscriptions and payment events.countries:readList supported countries.Endpoints
Core endpoints grouped by resource. Every endpoint requires the scope listed alongside it — a key without that scope receives a 403. Public endpoints (/v1/usage/, /v1/pricing-plans/) accept developer bearer tokens; the token-exchange endpoint accepts the API key pair.
Account
/v1/developer-token/Exchange an API key and secret for a short-lived bearer token.API key pair/v1/profile/Retrieve the authenticated account profile.profile:read/v1/profile/{id}/Update first name, phone number, or country.profile:writeAlert destinations
/v1/alert-destinations/List registered alert destinations.destinations:read/v1/alert-destinations/Register an alert destination (email, webhook).destinations:write/v1/alert-destinations/{id}/Remove an alert destination.destinations:write/v1/alert-destinations/join-whatsapp-group/Have the bot join a WhatsApp group and register it as a destination.destinations:whatsapp:joinAlert subscriptions
/v1/alert-subscriptions/List active alert subscriptions.subscriptions:read/v1/alert-subscriptions/Subscribe a destination to an alert stream.subscriptions:writeIntelligence
/v1/risk-assessments/Fetch the latest country risk assessments.risk_assessments:read/v1/alert-dispatches/Read the alert delivery history.dispatches:read/v1/supported-countries/List supported countries.countries:readPlan & usage
/v1/usage/Plan, limits, and today's per-app usage. Never counts against the quota.—/v1/pricing-plans/Public list of available pricing plans.—/v1/subscriptions/Your current billing subscriptions.billing:readRequests & responses
Send JSON request bodies with a Content-Type: application/json header. Every response is wrapped in a consistent envelope so clients can branch on erc before reading the payload:
erc
1 on success, 0 on error.
msg
Human-readable message (success on 2xx, error description on 4xx/5xx).
data
The actual payload — object for retrieves, array for lists.
meta.usage
Present on every developer-token-authenticated success — your plan, daily limit, and remaining quota (see Quotas & usage).
Request
POST /v1/alert-destinations/ { "label": "Ops channel", "channel": "email", "identifier": "ops@example.org" }
Response · 201 Created
{
"erc": 1,
"msg": "success",
"total": 1,
"next": null,
"data": {
"id": "d_8fa2",
"label": "Ops channel",
"status": "pending"
},
"meta": { "usage": { … } }
}Pagination
List endpoints are paginated. Pass page and page_size as query parameters. The envelope's top-level total and next fields give you the page count + cursor; the array of rows lives in data.
GET /v1/alert-dispatches/?page=2&page_size=50 { "erc": 1, "msg": "success", "total": 142, "next": "…/v1/alert-dispatches/?page=3", "data": [ … ] }
Quotas & usage
Civic Sentinel meters two things per plan: per-app API requests (counted from 00:00 UTC) and alerts delivered per day. Plans also cap how many applications and WhatsApp destinations you can register.
There are three ways to read your live quota — pick whichever fits your integration. All three return the same numbers.
1. Response headers
Every developer-token response carries the snapshot in standard rate-limit headers. X-RateLimit-Reset is epoch seconds for the next 00:00 UTC, when counters zero out.
X-PlansignalActive plan slug.X-RateLimit-Limit30Requests allowed per app per day.X-RateLimit-Used8Requests recorded so far today (includes this one).X-RateLimit-Remaining22Requests left before throttling.X-RateLimit-Reset1747353600Epoch seconds — when the counter resets.Retry-After27432On 429 only — seconds to wait.All five X-* headers are listed in Access-Control-Expose-Headers so SDKs and integrations can read them.
2. meta.usage in the response envelope
The same numbers, in JSON, on every successful developer-token response — useful when you can't (or don't want to) inspect headers.
{
"erc": 1,
"msg": "success",
"data": { … },
"meta": {
"usage": {
"plan": "signal",
"plan_name": "Signal",
"application_id": "a22c247a-…",
"application_name": "Election Insights",
"limit_per_day": 30,
"used_today": 8,
"remaining_today": 22,
"resets_at": "2026-05-17T00:00:00+00:00"
}
}
}3. GET /v1/usage/
Poll this whenever you want a snapshot without making any other call. This endpoint is exempt from the daily quota — you can hit it even while throttled.
curl https://api.civicsentinel.io/v1/usage/ \ -H "Authorization: Bearer $CIVIC_SENTINEL_TOKEN" # response.data { "plan": { "slug": "signal", "name": "Signal", "price_coins": 10 }, "limits": { "max_apps": 5, "max_api_requests_per_app_per_day": 30, … }, "today": { "alerts_used": 3, "resets_at": "2026-05-17T00:00:00+00:00"}, "applications": [ { … } ] }
When you hit the cap
The API responds with 429 Too Many Requests and a populated Retry-After header (seconds until 00:00 UTC). Back off until then, or upgrade your plan.
HTTP/1.1 429 Too Many Requests Retry-After: 27432 { "erc": 0, "msg": "Daily API quota for application … has been reached. Upgrade your plan or wait until tomorrow (UTC)." }
Capacity caps (creating more apps or WhatsApp destinations than your plan allows) return 403 instead — they aren't time-based, so retrying won't help; you need to upgrade.
Errors
Standard HTTP status codes. Error bodies use the same envelope with erc: 0 and a human-readable msg — either a string or, on validation errors, a { field: "…" } map.
{ "erc": 0, "msg": "Invalid API credentials." }400Bad RequestThe request body or query parameters were invalid.401UnauthorizedMissing, invalid, or expired bearer token; or invalid token-exchange credentials.403ForbiddenCredentials are valid but lack the required scope, or your plan does not allow the action.404Not FoundThe requested resource does not exist.429Too Many RequestsDaily per-app quota exhausted. Read X-RateLimit-Reset / Retry-After and try again later.500Server ErrorSomething went wrong on our side. Retry with backoff.Versioning
The API is versioned in the URL path. Additive changes — new fields, new endpoints, new envelope keys like meta.usage — ship without a version bump, so always code defensively against unknown fields. Breaking changes ship as a new version (for example /v2) and the previous version stays supported during a documented migration window.
v1Ready to issue your first key?
Open the Developer tab in your console to get started.
