Version 1.0 — 9 September 2026
The public API lets your own systems — a records system, a control-room dashboard, a toll or parking back-office — read what Suraag has already found in your footage and submit new footage for analysis. It is a small, deliberately read-heavy API: six endpoints, one header, JSON in and out.
Base URL: https://suraag.xyz/api/v1 (a dedicated or on-premises deployment has its own host; the path is the same).
1. Authentication
Every request carries an API key in the X-Api-Key header:
X-Api-Key: vf_<your key>
- Keys are issued by your organisation's Suraag administrator (see the Admin guide, "API keys"). Suraag stores only a SHA-256 hash of the key — a lost key cannot be recovered, only replaced.
- Every key belongs to one organisation and can only ever see that organisation's cameras and findings. A finding that belongs to someone else returns
404, not403, so the API never confirms that another tenant's record exists. - Every key carries scopes. A request to an endpoint whose scope the key lacks returns
403with the missing scope named in the message.
| Scope | Grants |
|---|---|
plate:read | GET /search/plate |
attrs:read | GET /search/attributes |
finding:read | GET /finding/{id}, GET /finding/{id}/crop |
ingest | POST /jobs, GET /jobs/{id} |
Every call is written to your organisation's audit trail (actor apikey:<org>, the action, the target and the caller's IP), the same trail the console shows under Audit.
2. Endpoints
2.1 GET /search/plate — plate timeline
Returns every trustworthy sighting of a number plate across your cameras, newest first. Only reads at or above the platform's plate-confidence threshold are returned; low- confidence garbage reads are never surfaced.
| Query parameter | Type | Default | Meaning |
|---|---|---|---|
plate | string | required | Plate text. Case-insensitive; upper-cased before matching. |
fuzzy | bool | true | true = partial match (substring), false = exact match. |
limit | int 1–200 | 50 | Maximum results. |
curl -s "https://suraag.xyz/api/v1/search/plate?plate=TS09&fuzzy=true&limit=20" \
-H "X-Api-Key: vf_xxxxxxxxxxxxxxxx"
{
"plate": "TS09",
"count": 2,
"results": [
{
"finding_id": "f_01J…",
"plate": "TS09EA1234",
"confidence": 0.93,
"camera_id": "cam_junction_north",
"ts_start": 412.8,
"wall_start": "2026-09-08T18:42:11+05:30",
"crop_url": "/api/v1/finding/f_01J…/crop"
}
]
}
ts_start is seconds from the start of the analysed file; wall_start is the wall-clock time if the footage carried one (DVR export time or the edge agent's recording time), otherwise null.
2.2 GET /search/attributes — structured attribute search
Finds objects by class and attributes. All parameters are AND-combined. Attribute names and values are the ones the console's catalogue exposes (helmet, color, vehicle_type, …); gated attributes such as weapon_candidate are excluded from API results unless your key has been extended by support.
| Query parameter | Type | Meaning |
|---|---|---|
camera_id | string | Restrict to one camera. |
object_kind | string | person, vehicle, object, … |
cls | string | Detector class, e.g. motorcycle, car, auto_rickshaw. |
attrs | JSON string | Attribute filter, e.g. {"color":"red","helmet":"no"}. |
time_from, time_to | ISO-8601 datetime | Wall-clock window. |
limit | int 1–200 | Default 50. |
curl -s -G "https://suraag.xyz/api/v1/search/attributes" \
--data-urlencode 'cls=motorcycle' \
--data-urlencode 'attrs={"helmet":"no"}' \
--data-urlencode 'time_from=2026-09-08T00:00:00+05:30' \
-H "X-Api-Key: vf_xxxxxxxxxxxxxxxx"
Each result carries finding_id, camera_id, cls, object_kind, ts_start, wall_start, conf, attributes (the full attribute dictionary), a one-line caption with caption_source (vlm when a vision-language model wrote it, auto when it is a template), and crop_url (or null if no crop was stored).
A malformed attrs value returns 400 attrs must be a valid JSON object.
2.3 GET /finding/{finding_id} — finding detail
Everything Suraag holds about one detection: bounding box, timing, confidence, attributes, caption, and the plate read if there is one.
curl -s "https://suraag.xyz/api/v1/finding/f_01J…" -H "X-Api-Key: vf_xxxxxxxxxxxxxxxx"
{
"finding_id": "f_01J…",
"camera_id": "cam_junction_north",
"job_id": "6c1f…",
"cls": "motorcycle",
"object_kind": "vehicle",
"ts_start": 412.8, "ts_end": 416.1,
"wall_start": "2026-09-08T18:42:11+05:30",
"bbox": [0.41, 0.22, 0.58, 0.61],
"conf": 0.88,
"attributes": {"helmet": "no", "riders": "2", "color": "black"},
"caption": "A black motorcycle with two riders, no helmet",
"caption_source": "auto",
"plate": {"text": "TS09EA1234", "confidence": 0.93, "present": true},
"crop_url": "/api/v1/finding/f_01J…/crop"
}
Unknown ids, and ids that belong to another organisation, return 404 finding not found.
2.4 GET /finding/{finding_id}/crop — evidence crop
Returns the stored JPEG crop for a finding as raw bytes (Content-Type: image/jpeg).
curl -s "https://suraag.xyz/api/v1/finding/f_01J…/crop" \
-H "X-Api-Key: vf_xxxxxxxxxxxxxxxx" -o crop.jpg
404 crop not found when the finding has no stored crop; 502 crop unavailable in object storage if the object store cannot serve it right now — retry later.
2.5 POST /jobs — submit footage for analysis
Queues a video that is already reachable by the Suraag worker (a server-side path or an object-storage key agreed with support at onboarding — the API does not accept a video body; use the console or the edge agent to upload files). Returns 202 Accepted.
curl -s -X POST "https://suraag.xyz/api/v1/jobs" \
-H "X-Api-Key: vf_xxxxxxxxxxxxxxxx" -H "Content-Type: application/json" \
-d '{"camera_id":"cam_junction_north","video_path":"ingest/2026-09-08/north.mp4","fps":2}'
| Body field | Type | Meaning |
|---|---|---|
camera_id | string, required | The camera the footage belongs to (must be yours). |
video_path | string, required | Server path or object-storage key of the file. |
fps | number, optional | Sampling rate for analysis (default: platform profile). |
max_seconds | number, optional | Analyse only the first N seconds. |
{"queued": true, "job_id": "arq:…", "status_url": "/api/v1/jobs/arq:…"}
2.6 GET /jobs/{job_id} — job status
curl -s "https://suraag.xyz/api/v1/jobs/arq:…" -H "X-Api-Key: vf_xxxxxxxxxxxxxxxx"
{
"job_id": "6c1f…", "camera_id": "cam_junction_north",
"status": "running", "progress": 0.42,
"frames_total": 3000, "frames_done": 1260, "findings_count": 187,
"error": null,
"created_at": "2026-09-08T18:50:02+05:30", "updated_at": "2026-09-08T18:52:40+05:30"
}
status is one of queued, running, done, error. While the GPU analysis engine is offline, jobs stay queued and are processed when it starts — nothing is lost.
3. Errors
Errors are JSON: {"detail": "<message>"}.
| Code | Meaning |
|---|---|
400 | A parameter could not be parsed (for example attrs is not valid JSON). |
401 | Missing, unknown or revoked API key. |
403 | The key is valid but lacks the required scope. |
404 | The finding, crop or job does not exist — or is not yours. |
422 | Request body failed validation (missing camera_id, wrong type). |
429 | Rate limit exceeded — back off and retry after the Retry-After header if present. |
502 | Object storage could not serve the crop; retry. |
4. Rate limits and good behaviour
- The public API sits behind a per-organisation rate-limit policy at the reverse proxy (on the order of 60 requests per minute per key). Implement exponential back-off on
429and on network errors. - Poll
GET /jobs/{id}no more often than every 10 seconds; a typical clip takes minutes, not seconds. - Cache crops on your side — they never change once written.
- Treat
plateandattributesas investigative leads: a plate read carries its confidence for a reason, and an attribute is a model's estimate. Nothing the API returns is an identification of a person. - Keep keys out of source control and browsers. A key that leaks should be revoked by your administrator immediately and a new one issued.
5. Changes
| Date | Change |
|---|---|
| 2026-09-09 | First published reference (v1). |