Most of SecurityTrackr assumes a person at the keyboard: you describe a finding, the AI drafts it, you review and save. The REST API is the programmatic door for everything else that needs in: a script, a scanner, a SIEM pipeline, or an agent. The versioned REST API below is authenticated with a bearer token; see wiring an agent in with skills for the practical wiring if your own caller happens to be an agent working from a skill.
The REST API
It's a versioned REST API under /api/v1. Requests and responses are JSON in snake_case. Every query is scoped to your organisation automatically, derived from the token you present and never from anything in the request body, so an endpoint can only ever address your own tenant.
The important design call is that the API is not a thin passthrough. POST /api/v1/observations/preflight starts the same duplicate-review and create pipeline the manual wizard uses. You review every candidate it returns, then finalize the attempt. The API applies the same public review rules as the in-app flow, and the result enters the same human-visible register and audit trail. Direct POST /observations is deliberately refused with 428.
Bearer tokens, scoped to a role
Every request carries a bearer token over HTTPS: no cookies, no session, no consent screen to redirect through. An owner creates the token in the UI, and the caller sends it on each request:
Authorization: Bearer sgt_live_<keyId>_<secret>A couple of things worth knowing about that token:
- It's shown once, and we never store it. The secret is 256 bits of randomness, shown to you exactly once at creation. We only ever keep a one-way hash of it, so we can check a token but never reproduce it. Lose it and you create a new one; there's no recovery.
- The prefix makes a leak obvious. Every token starts with sgt_live_, so one committed to a repo or pasted into a log is easy to spot and revoke.
Tokens are role-scoped. General-purpose tokens carry exactly one role and are authorized as a user of that role would be; the token-only SIEM role is limited to audit export:
- auditor. Read-only. Can hit the GET endpoints, nothing else.
- contributor / member. Can create and update observations.
- siem. Token-only audit export. It can verify with GET /me and poll /audit-events, but cannot read or write observations or be assigned to a human.
- owner is not assignable. An owner-level bearer credential could manage billing, members, and other tokens, and delete the org. That's too much authority for a long-lived key, so tokens top out at member.
Pick the lowest role that does the job. A scanner that only files findings wants a contributor token; a dashboard that only reads wants an auditor token.
It's a Pro feature, capped at ten tokens
API access is gated to the Pro plan. That gate is checked in two places: when you manage tokens, and again on every /api/v1 request. If an org drops off Pro after a token was created, that token stops working and returns a 403 PLAN_REQUIRED, even though the token itself is still valid. The plan is the licence to use the API, not the token.
An organisation can have up to ten active tokens at once, regardless of seat count. Tokens are created, listed, and revoked by an owner under Account → API Tokens. Each one defaults to 90 days; you can choose 30 days, 90 days, 1 year, or never (the UI warns you on never). The tab shows each token's last-used time so you can spot a stale one and rotate it.
What you can call
Everything lives under https://app.securitytrackr.com/api/v1. The endpoints cover the full workflow: prepare and review a finding, read it back, record what was done, attach the evidence and discussion around it, link related findings, then re-score it, so a human reading the register sees what your integration reported and the current risk.
| GET | /me | Liveness and capability probe: your org id, the token's role, and its rate limits. |
| GET | /observations | List triage summaries (title + status/risk fields), keyset-paginated, defaulting to open findings. Full body via GET {id}. |
| POST | /observations | Direct creation is refused with 428. Use preflight and finalize so every candidate is reviewed. |
| POST | /observations/preflight | Prepare a duplicate-review attempt for a proposed observation. |
| GET | /observations/ingest-attempts/{attemptId} | Read the current state and candidates for an ingest attempt. |
| POST | /observations/ingest-attempts/{attemptId}/finalize | Finalize a reviewed proposal and create or link the observation. |
| GET | /observations/search | Search observation candidates by keyword. |
| GET | /observations/{id} | Read one full observation. An unknown or other-org id is a 404. |
| PATCH | /observations/{id} | Update fields and recommendation statuses. No AI, field update only. |
| GET | /observations/{id}/comments | List an observation's comments, newest first. |
| POST | /observations/{id}/comments | Add a comment as the authenticated user. |
| PATCH | /observations/{id}/comments/{commentId} | Edit your own comment. |
| DELETE | /observations/{id}/comments/{commentId} | Delete a comment; authors and organisation owners can do this. |
| GET | /observations/{id}/relations | List relations from an observation's perspective. |
| POST | /observations/{id}/relations | Create or replace a directional relation between observations. |
| DELETE | /observations/{id}/relations/{relationId} | Delete an observation relation. |
| GET | /observations/{id}/evidence | List attached evidence metadata and scan status. |
| POST | /observations/{id}/evidence | Upload one evidence file as multipart/form-data. |
| GET | /observations/{id}/evidence/{evidenceId} | Download evidence after its scan permits access. |
| DELETE | /observations/{id}/evidence/{evidenceId} | Delete an evidence file. |
| POST | /observations/{id}/reassess | Re-run the AI risk assessment and persist the new scores. |
| POST | /observations/{id}/recommendations/{recId}/deploy | Move a completed recommendation into existing controls. |
| POST | /observations/{id}/mark-mitigated | Mark the observation mitigated once its treatment is done. |
| GET | /ai/jobs/{jobId} | Poll durable AI work submitted by this credential. |
| POST | /ai/jobs/{jobId}/cancel | Cancel active durable AI work. |
| POST | /ai/jobs/{jobId}/acknowledge | Acknowledge a handled terminal AI outcome. |
| GET | /audit-events | Export the audit trail as OCSF events for a SIEM connector. |
The context around a finding is part of the API
An observation is rarely just a JSON row. The same REST surface lets an integration carry the conversation, the links between findings, and the files that support them. These resources use the observation id in the path and inherit the same tenant isolation and bearer authentication as the core observation endpoints.
- Comments. List and add comments at /observations/{id}/comments, then edit or delete a comment at /observations/{id}/comments/{commentId}. Editing is author-scoped; an organisation owner can delete any comment. Lists are cursor-paginated, newest first.
- Relations. List relations at /observations/{id}/relations, create or replace one with POST on that collection, and delete it at /observations/{id}/relations/{relationId}. Relations are directional and named, so a root cause, dependency, or related finding remains explicit rather than buried in prose.
- Evidence. List evidence metadata at /observations/{id}/evidence, upload a file with multipart/form-data using the file field, download it at /observations/{id}/evidence/{evidenceId}, and delete it with DELETE. Files are limited to 25 MiB, scanned before download, and are never base64-encoded into JSON.
The list endpoint is deliberately lightweight: it returns triage summaries (title, status, and risk fields), keyset-paginated with a cursor, and defaults to open findings. Fetch the full observation, with its summary, controls, and recommendations, from GET /observations/{id}. The write endpoints need a contributor or member token; the reads are fine for an auditor. Errors come back in one uniform envelope with a stable code (BAD_REQUEST, UNAUTHORIZED, RATE_LIMITED, and so on).
Confirm the token works, prepare the proposal, review every candidate, then finalize it. The server assesses, formats, scores, and links it for you.
# 1. Is the token alive, and what can it do?
curl -s https://app.securitytrackr.com/api/v1/me \
-H "Authorization: Bearer $TOKEN"
# -> { "organization_id": "c...", "role": "contributor",
# "rate_limit": { ... } }
# 2. Prepare the candidate review (contributor / member token).
curl -s -X POST https://app.securitytrackr.com/api/v1/observations/preflight \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: scan-42-tls-lb-prod-01" \
-d '{
"user_observation": "TLS 1.0 is still enabled on the edge load balancer.",
"affected_systems": "lb-prod-01"
}'
# -> 201 { "attempt_id": "c...", "review_revision": 1, "candidates": [...] }
# 3. Review every candidate, then finalize the exact revision.
curl -s -X POST https://app.securitytrackr.com/api/v1/observations/ingest-attempts/$ATTEMPT_ID/finalize \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"review_revision": 1,
"candidate_dispositions": []
}'
# -> 201 { "id": "c...", "identifier": "Obs042", "risk_rating": "High", ... }
# title, summary, likelihood/impact, recommendations, ISO/NIST areas and
# links to related findings are all filled in by the server.With enrichment on (the default) only user_observation is required. The server applies the same governed review process as the wizard, so an enriched finalize is a slow request because it may involve several AI steps. Use a generous client timeout and retry safely with the same idempotency key if the request is interrupted. Prefer enrich: false only when you want your exact values stored as-is with no AI step, in which case you supply likelihood and impact yourself and the risk rating is derived from them.
The OpenAPI spec, served straight from the app
You don't have to take any of the above on faith. The full contract is published as an OpenAPI 3.1 document, served unauthenticated:
GET https://app.securitytrackr.com/api/v1/openapi.jsonIt contains only the static API shape, no tenant data and no secrets, which is why it's safe to serve without a token. It's the artifact to hand an integrator: import it into Swagger UI, editor.swagger.io, or Postman to explore the endpoints, or feed it to an LLM as a set of tool definitions. It's kept in lockstep with the routes by a test that fails our build if the documented enums or endpoint set ever drift from what actually ships, so it can't silently go stale.
Two skills that teach an agent to use it
The OpenAPI JSON tells an agent what it can call. It doesn't tell it when or how. For the token-based REST path, that operating manual ships as a downloadable skill, in two variants, both served unauthenticated:
- Claude skill. A drop-in SKILL.md at /api/v1/skill/claude. A Claude or Claude Code agent auto-discovers it from a skills folder and loads it when a finding needs tracking.
- Generic guide. Provider-neutral Markdown at /api/v1/skill/generic, with a section on wiring the openapi.json into your agent. This is the one for OpenAI, Mistral, LangChain, and the rest.
Both are one-click downloads in Account → API Tokens, right next to where you create the token, and both are generated from the same source of truth as the OpenAPI spec so they can't describe the API differently. Dropping one of these plus a token into your pipeline is the whole integration. That's the next article: wiring an agent into SecurityTrackr with skills.
