Version: v1
Base URL:https://intel-api.sety.io/v1/external
Your platform Sety Intel
───────────── ──────────
POST /incidents → Receives incident
→ Dispatches nearest responder
← Webhooks: status updates
GET /incidents/:id ← Poll anytime for current status
POST /incidents/:id/cancel (if false alarm)subject — they don't need a Sety account. Pass whatever PII your platform already holds: name, phone, your internal reference ID, and any freeform metadata.| Category | Use for |
|---|---|
sos | Panic button, personal danger, user in distress |
vehicle | Hijack, road robbery, accident |
location_threat | Robbery or violence at a specific place |
medical | Injury, health emergency |
| Key prefix | Environment | Behavior |
|---|---|---|
sety_live_ | Production | Real dispatch, live responders |
sety_test_ | Sandbox | No real dispatch, safe for testing |
{
"status": "ok",
"api_version": "v1",
"timestamp": "2026-03-10T14:22:00.000Z"
}incident.* events. We'll POST to you every time an incident status changes.Authorization: Bearer sety_live_a3f9c2d18e4b7a6509c1d2e3f4a5b6c7sety_test_) during development and CI| Plan | Requests / minute |
|---|---|
| Starter | 60 |
| Growth | 300 |
| Enterprise | 1,000 |
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 287
X-RateLimit-Reset: 2026-03-10T14:31:00.000Z429 Too Many Requests with a Retry-After header. Back off and retry after that duration.{
"code": 404,
"message": "Incident not found"
}| Code | Meaning |
|---|---|
400 | Invalid request — message describes which field failed |
401 | Missing or invalid API key |
402 | Plan does not include API access, or subscription suspended |
403 | Key lacks required scope |
404 | Resource not found or belongs to a different organization |
409 | State conflict — e.g. cancelling an already-resolved incident |
429 | Rate limit exceeded |
500 | Unexpected server error — contact support |
created → dispatching → assigned → responding → on_scene → resolved
│
└──── escalated ──── unresponsivecancelled if you call the cancel endpoint.| Status | What it means |
|---|---|
created | Received — dispatch starting |
dispatching | Broadcasting to Sety responder network |
assigned | A responder has accepted |
responding | Responder confirmed en route |
on_scene | Responder has arrived |
resolved | Incident closed |
cancelled | Cancelled by your platform or Sety |
escalated | No responder accepted within the dispatch window |
unresponsive | Escalated — still no response after the escalation window |
POST /api/v1/incidentsincidents:write| Field | Type | Required | Description |
|---|---|---|---|
category | string | ✅ | sos, vehicle, location_threat, or medical |
severity | string | ✅ | low, medium, high, or critical |
subject | object | ✅ | Your customer — see Subject object below |
location | object | ✅ | Where the incident is happening |
title | string | Short title. Auto-generated if omitted | |
description | string | Additional context. Max 2,000 chars | |
idempotency_key | string | Unique key for safe retries. Max 128 chars | |
is_drill | boolean | Default false. Drills don't trigger real dispatch |
| Field | Type | Required | Description |
|---|---|---|---|
name | string | ✅ | Your customer's full name |
phone | string | Phone number | |
email | string | Email address | |
reference | string | Your internal ID — trip ID, user ID, waybill, etc. Echoed back in all responses and webhook payloads | |
metadata | object | Up to 20 key-value string pairs of freeform context |
| Field | Type | Required | Description |
|---|---|---|---|
coordinates | number[2] | ✅ | [longitude, latitude] — GeoJSON order |
address | string | Human-readable address | |
landmark | string | Nearby landmark to help the responder |
⚠️ Coordinates are [longitude, latitude]— this is GeoJSON convention, which is the reverse of the common lat/lng order.
idempotency_key to safely retry a request without creating duplicate incidents. If a key matches an existing incident for your organization, the existing incident is returned with HTTP 200 and "idempotent": true. Use a value that's naturally unique per incident attempt — a trip ID, session ID, or UUID you generate.{
"category": "sos",
"severity": "critical",
"idempotency_key": "session_abc123",
"subject": {
"name": "Amaka Obi",
"phone": "+2348012345678",
"reference": "USER-9921"
},
"location": {
"coordinates": [3.3792, 6.5244],
"address": "14 Bourdillon Road, Ikoyi, Lagos"
}
}{
"category": "vehicle",
"severity": "critical",
"idempotency_key": "TRIP-44821",
"subject": {
"name": "Emeka Adeyemi",
"phone": "+2348099887766",
"reference": "TRIP-44821",
"metadata": {
"vehicle_plate": "LND-204-KJA",
"driver_name": "Olu Babs",
"pickup_area": "Lekki Phase 1"
}
},
"location": {
"coordinates": [3.3512, 6.4698],
"landmark": "Eko Bridge, Lagos"
},
"description": "Driver reports passengers attempted armed robbery"
}{
"category": "location_threat",
"severity": "high",
"subject": {
"name": "Branch Security",
"reference": "BRANCH-VI-01"
},
"location": {
"coordinates": [3.4219, 6.4281],
"address": "1 Ozumba Mbadiwe Ave, Victoria Island",
"landmark": "GTBank branch ATM vestibule"
},
"description": "Armed men demanding cash from customers at ATM"
}201{
"incident": {
"incident_id": "inc_a1b2c3d4e5f6g7h8",
"category": "vehicle",
"severity": "critical",
"status": "dispatching",
"subject": {
"name": "Emeka Adeyemi",
"phone": "+2348099887766",
"reference": "TRIP-44821",
"metadata": {
"vehicle_plate": "LND-204-KJA"
}
},
"location": {
"coordinates": [3.3512, 6.4698],
"landmark": "Eko Bridge, Lagos"
},
"title": "[CRITICAL] Vehicle Emergency",
"description": "Driver reports passengers attempted armed robbery",
"is_drill": false,
"timeline": [
{
"status": "created",
"event": "created",
"message": "Incident received",
"timestamp": "2026-03-10T14:22:00.000Z"
},
{
"status": "dispatching",
"event": "dispatched",
"message": "Dispatching to Sety responder network",
"timestamp": "2026-03-10T14:22:00.120Z"
}
],
"resolution_outcome": null,
"created_at": "2026-03-10T14:22:00.000Z",
"updated_at": "2026-03-10T14:22:00.120Z",
"resolved_at": null,
"cancelled_at": null
}
}200 — idempotent (key already used){
"incident": { ... },
"idempotent": true
}GET /api/v1/incidentsincidents:read| Parameter | Type | Description |
|---|---|---|
status | string | Filter by status |
category | string | Filter by category |
severity | string | Filter by severity |
from_date | string | ISO 8601 |
to_date | string | ISO 8601 |
page | integer | Default 1 |
limit | integer | Default 20, max 100 |
200{
"results": [ /* array of incident objects */ ],
"page": 1,
"limit": 20,
"totalPages": 3,
"totalResults": 47
}GET /api/v1/incidents/:incidentIdincidents:read200 — incident object (same shape as create response)POST /api/v1/incidents/:incidentId/cancelincidents:write| Field | Type | Description |
|---|---|---|
reason | string | Optional. Recorded in the incident timeline |
{
"reason": "User confirmed they are safe — false alarm"
}200 — updated incident with status: "cancelled"409 — incident already closed{
"code": 409,
"message": "Incident cannot be cancelled — current status: resolved"
}POST /api/v1/incidents/:incidentId/notesincidents:write| Field | Type | Required | Description |
|---|---|---|---|
message | string | ✅ | Note content. Max 2,000 chars |
{
"message": "User called back — now at Lekki Phase 1 roundabout, vehicle is a white Toyota Camry"
}200 — updated incident with note appended to timelinePOST /api/v1/access-codesaccess_codes:write| Field | Type | Required | Description |
|---|---|---|---|
visitor_name | string | ✅ | |
visitor_phone | string | ✅ | |
visitor_email | string | ||
purpose | string | Reason for visit | |
entry_point | string | Gate or entrance identifier | |
host_user_id | string | Staff member hosting the visitor | |
valid_from | string | ISO 8601. Defaults to now | |
valid_until | string | ISO 8601 expiry | |
max_uses | integer | Default 1 |
{
"visitor_name": "Adaeze Okonkwo",
"visitor_phone": "+2348012345678",
"visitor_email": "[email protected]",
"purpose": "Board meeting",
"entry_point": "Main Gate",
"valid_from": "2026-03-11T09:00:00.000Z",
"valid_until": "2026-03-11T18:00:00.000Z",
"max_uses": 1
}201{
"access_code": {
"code_id": "code_abc123...",
"code": "SETY-7X4K",
"visitor_name": "Adaeze Okonkwo",
"visitor_phone": "+2348012345678",
"status": "active",
"valid_from": "2026-03-11T09:00:00.000Z",
"valid_until": "2026-03-11T18:00:00.000Z",
"max_uses": 1,
"use_count": 0,
"created_at": "2026-03-10T14:00:00.000Z"
}
}POST /api/v1/access-codes/bulkpurpose, valid_from, and valid_until.access_codes:write| Field | Type | Required | Description |
|---|---|---|---|
visitors | object[] | ✅ | Array of visitor objects (max 50). Each needs visitor_name and visitor_phone |
purpose | string | Shared purpose for all | |
valid_from | string | ||
valid_until | string |
POST /api/v1/access-codes/validatevalid field — an invalid code returns 200 with valid: false, not a 4xx error.access_codes:validate| Field | Type | Required | Description |
|---|---|---|---|
code | string | ✅ | The code to validate (e.g. SETY-7X4K) |
validated_by | string | Name or ID of the officer validating |
200 — valid{
"valid": true,
"code_id": "code_abc123...",
"visitor": {
"name": "Adaeze Okonkwo",
"phone": "+2348012345678"
},
"purpose": "Board meeting",
"uses_remaining": 0,
"valid_until": "2026-03-11T18:00:00.000Z"
}200 — invalid{
"valid": false,
"reason": "Code has expired"
}reason values: Code not found · Code has expired · Code has been revoked · Maximum uses reached · Code not yet activeGET /api/v1/access-codesaccess_codes:readstatus, visitor_phone, search, from_date, to_date, page, limitGET /api/v1/access-codes/:codeIdaccess_codes:readDELETE /api/v1/access-codes/:codeIdCode has been revoked.access_codes:write| Field | Type | Description |
|---|---|---|
reason | string | Optional |
GET /api/v1/access-codes/statsaccess_codes:readfrom_date, to_date200{
"total_generated": 320,
"total_used": 281,
"total_expired": 24,
"total_revoked": 3,
"total_active": 12
}POST /api/v1/access-logsaccess_codes:write| Field | Type | Required | Description |
|---|---|---|---|
visitor_name | string | ✅ | |
visitor_phone | string | ✅ | |
entry_point | string | ||
host_user_id | string | ||
notes | string |
GET /api/v1/access-logslogs:readstatus (checked_in or checked_out), visitor_phone, entry_point, from_date, to_date, search, page, limitGET /api/v1/access-logs/current-visitorslogs:read200{
"visitors": [
{
"log_id": "log_abc...",
"visitor_name": "Adaeze Okonkwo",
"visitor_phone": "+2348012345678",
"entry_point": "Main Gate",
"checked_in_at": "2026-03-10T09:15:00.000Z",
"duration_minutes": 187
}
],
"count": 1
}PATCH /api/v1/access-logs/:logId/checkoutaccess_codes:write| Field | Type | Description |
|---|---|---|
checkout_notes | string | Optional |
GET /api/v1/access-logs/statslogs:readfrom_date, to_dateGET /api/v1/safety/scoreanalytics:read| Parameter | Type | Required | Description |
|---|---|---|---|
latitude | number | ✅ | |
longitude | number | ✅ | |
radius_km | number | Analysis radius. Default 2 |
200{
"location": { "latitude": 6.4281, "longitude": 3.4219 },
"score": 72,
"level": "moderate",
"color": "yellow",
"breakdown": {
"recent_incidents_7d": 1,
"recent_incidents_30d": 4,
"trend": "stable"
},
"timestamp": "2026-03-10T14:22:00.000Z"
}| Score | Level | Color | Meaning |
|---|---|---|---|
| 70–100 | safe | green | Low incident history |
| 50–69 | moderate | yellow | Some activity — monitor |
| 30–49 | high_risk | orange | Active threat area |
| 0–29 | critical | red | Avoid if possible |
GET /api/v1/safety/routeanalytics:read| Parameter | Type | Required | Description |
|---|---|---|---|
from_lat | number | ✅ | Origin latitude |
from_lng | number | ✅ | Origin longitude |
to_lat | number | ✅ | Destination latitude |
to_lng | number | ✅ | Destination longitude |
checkpoints | integer | Sampling points along route. Default 10, max 20 |
200{
"route_safety_score": 68,
"lowest_safety_score": 41,
"checkpoints": [
{ "checkpoint": 1, "location": { "lat": 6.52, "lng": 3.37 }, "safety_score": 81, "level": "safe" },
{ "checkpoint": 6, "location": { "lat": 6.46, "lng": 3.39 }, "safety_score": 41, "level": "high_risk" }
],
"danger_zones": [
{ "checkpoint": 6, "score": 41, "warning": "High risk area — exercise caution" }
],
"recommendation": "⚠️ Exercise caution — dangerous segment detected along route",
"alternative_needed": false
}GET /api/v1/danger-zones/analyticsanalytics:readlatitude ✅, longitude ✅, radius_km (default 5)200{
"location": { "latitude": 6.4281, "longitude": 3.4219 },
"radius_km": 5,
"analytics": {
"danger_level": "medium",
"total_incidents": 47,
"last_7days": 3,
"last_30days": 11,
"last_90days": 32,
"top_incident_types": ["theft", "suspicious_activity", "vandalism"],
"trend": "stable"
}
}GET /api/v1/danger-zones/heatmapanalytics:read| Parameter | Type | Required | Description |
|---|---|---|---|
bounds | string | ✅ | "lat1,lng1,lat2,lng2" bounding box |
days | integer | Lookback period. Default 90, max 365 |
GET /api/v1/danger-zones/heatmap?bounds=6.35,3.30,6.70,3.55&days=30200{
"count": 128,
"heatmap": [
{ "lat": 6.5244, "lng": 3.3792, "weight": 7, "type": "theft" }
]
}GET /api/v1/danger-zonesanalytics:readincident_type, severity_min (1–10), latitude, longitude, radius_km, from_date, to_date, page, limit| Event | Trigger |
|---|---|
incident.created | Incident successfully received |
incident.updated | Status changed (dispatching → assigned → responding → on_scene) |
incident.escalated | No responder accepted in time |
incident.unresponsive | Still no response after escalation window |
incident.resolved | Incident closed by Sety |
incident.cancelled | Incident cancelled |
access_code.created | Code generated |
access_code.used | Code successfully validated |
access_code.expired | Code passed expiry date |
access_code.revoked | Code manually revoked |
visitor.arrived | Visitor checked in |
visitor.departed | Visitor checked out |
{
"id": "evt_a1b2c3d4...",
"type": "incident.updated",
"created_at": "2026-03-10T14:23:10.000Z",
"data": {
// Incident or access code object — same shape as the REST response
}
}Sety-Signature header. Verify it on your server to confirm the payload came from Sety.Sety-Signature: t=1741613220,v1=a3f9c2d18e4b7a6509c1...2xx is treated as success — respond immediately and process asyncevent.id to deduplicate in case of retries| Scope | What it grants |
|---|---|
incidents:read | List and get incidents |
incidents:write | Create incidents, cancel, add notes |
access_codes:read | Read codes and stats |
access_codes:write | Generate, bulk generate, revoke codes. Create and update access logs |
access_codes:validate | Validate (consume) a code at a gate |
logs:read | Read access logs and current visitors |
analytics:read | Safety scores, route analysis, danger zone data, heatmaps |
incidents:read
incidents:write
analytics:readaccess_codes:read
access_codes:write
access_codes:validate
logs:readincidents:read
incidents:write
access_codes:read
access_codes:write
access_codes:validate
logs:read
analytics:read