API Reference
Complete reference for the SupportOS REST API. Base URL: https://api.support-os.net
Authentication
All API requests must be authenticated. There are two authentication methods: **JWT Bearer Tokens** — Used by the dashboard and programmatic clients. Obtain a token via the /auth/token endpoint. Include it in the Authorization header: `Authorization: Bearer <token>`. Tokens are tenant-scoped, RS256-signed, and expire after 1 hour. **API Keys** — Used for webhook ingestion and server-to-server calls. API keys are created in Settings > API Keys and are scoped to a single tenant. Include the key in the `x-api-key` header. API keys do not expire but can be revoked at any time. All requests must include a `Content-Type: application/json` header. Rate limits apply: 100 requests/minute for standard endpoints, 500/minute for webhook ingestion.
Webhooks
Ingest events from external platforms. All webhook payloads must be signed with HMAC-SHA256. Compute the signature over the raw request body using your webhook secret and include it in the `X-Signature-256` header. Events are processed idempotently based on `event_id`.
/api/v1/webhooks/eventsX-API-Key + X-Signature-256 (HMAC-SHA256)Receive an event from an external platform. Supported event types: user_reported, message_flagged, csam_hash_match, account_suspended, content_removed, user_blocked, appeal_submitted. Each event is deduplicated by event_id.
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| event_id | string (UUID) | required | Unique identifier for this event — used for idempotency. |
| event_type | string | required | One of: user_reported, message_flagged, csam_hash_match, account_suspended, content_removed, user_blocked, appeal_submitted. |
| timestamp | string (ISO 8601) | required | When the event occurred on the source platform. |
| source | string | required | Identifier for the source platform (e.g. 'dating-frame'). |
| payload | object | required | Event-specific data. Schema varies by event_type. |
| payload.reporter_id | string | optional | ID of the reporting user (user_reported events). |
| payload.reported_user_id | string | optional | ID of the reported user. |
| payload.reason | string | optional | Reason for the report. |
| payload.content | object | optional | Content associated with the event (messages, images, etc.). |
| payload.hash_value | string | optional | Hash value for csam_hash_match events. |
| payload.hash_algorithm | string | optional | Hash algorithm used (e.g. 'photodna', 'md5'). |
Example Request
curl -X POST https://api.support-os.net/api/v1/webhooks/events \
-H "Content-Type: application/json" \
-H "X-API-Key: sk_live_abc123def456" \
-H "X-Signature-256: sha256=a1b2c3d4e5f6..." \
-d '{
"event_id": "evt_9f8e7d6c-5b4a-3c2d-1e0f-a1b2c3d4e5f6",
"event_type": "user_reported",
"timestamp": "2026-04-02T14:30:00.000Z",
"source": "dating-frame",
"payload": {
"reporter_id": "usr_a1b2c3d4",
"reported_user_id": "usr_e5f6g7h8",
"reason": "inappropriate_content",
"content": {
"type": "message",
"text": "Reported message content here",
"message_id": "msg_x1y2z3"
}
}
}'Example Response
{
"id": "tkt_3a2b1c0d-9e8f-7a6b-5c4d-3e2f1a0b9c8d",
"event_id": "evt_9f8e7d6c-5b4a-3c2d-1e0f-a1b2c3d4e5f6",
"status": "received",
"created_at": "2026-04-02T14:30:01.123Z"
}Error Responses
| Status | Description |
|---|---|
| 400 | Invalid payload — missing required fields or unknown event_type. |
| 401 | Missing or invalid API key. |
| 403 | HMAC signature verification failed. |
| 409 | Event with this event_id has already been processed (idempotent — safe to retry). |
| 429 | Rate limit exceeded. Retry after the duration in the Retry-After header. |
Tickets
Manage support tickets created from webhooks, user reports, or manual entry. Tickets are classified by AI and can have AI-generated draft responses.
/api/v1/ticketsBearer JWT (agent, safeguarding_lead or admin)List tickets for the current tenant as one inbox with three views. to_do holds open, pending, pending_agent and escalated tickets (there is no separate escalated list); waiting holds pending_customer; done holds resolved and referred_to_authority. Merged tickets are excluded. When view is present it replaces status filtering; status, needsReply and autoResolved still work for existing clients. Sort: to_do puts CSEA-flagged tickets first, then other priority offences or tickets linked to a safety case, then priority (critical to low), then the longest customer wait. waiting sorts by lastOutboundAt ascending; done by resolvedAt (or updatedAt) descending. Pagination is cursor-based: pass the returned cursor to get the next page. Each row is the ticket plus autoReplied, messageCount, nextAction (safety_review, escalated, check_category, review_draft, customer_replied, new, needs_reply, waiting_on_customer or resolved), nextActionReason, waitingSince (latest inbound message, else createdAt), lastOutboundAt, assignee ({ id, name } or null), slaDueAt (earliest deadline of an SLA still owed, including the linked safety case's timers: a running timer, or a breached one not yet met, so a past time means overdue), safetyFlag, hasPendingDraft, resolvedBy (ai or human in done, otherwise null) and resolvedAt (from the resolution outcome, else null). Without view, the legacy list applies: only status, needsReply, autoResolved, category, priority, search and includeMerged are read, the cursor is a ticket ID and the default limit is 20.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| view | string | optional | to_do, waiting or done. When present, status, needsReply and autoResolved are ignored. |
| assignee | string | optional | me, unassigned, or a user ID (UUID). View only. |
| safety | boolean | optional | When true, only tickets with a priority offence flag, a linked safety case, or a safety category (csea_concern, underage_risk, suicide_self_harm, abuse_harassment, cyberflashing_intimate_image, sexual_exploitation, scam_fraud, extreme_content, hate_speech). View only. |
| draftReady | boolean | optional | When true, only tickets with a pending AI draft created after the latest inbound message. View only. |
| resolvedBy | string | optional | ai or human. Applied only when view=done; ignored otherwise. |
| priority | string | optional | Filter by priority: low, medium, high, critical. |
| category | string | optional | Filter by category. Must be a valid ticket category. |
| search | string | optional | Up to 200 characters. Matches subject, body, userIdentifier and customer email. |
| includeMerged | boolean | optional | When true, merged tickets are included. They are hidden by default. |
| status | string | optional | Legacy. Filter by status: open, pending_agent, pending_customer, escalated, resolved, referred_to_authority. Ignored when view is present. |
| needsReply | boolean | optional | Legacy. When true, return only open or pending_agent tickets. Prefer view=to_do. |
| cursor | string | optional | Opaque cursor from the previous page, valid only for the view that returned it. Omit for the first page. |
| limit | number | optional | Number of results per page (1–100, default 50 with view, 20 without). |
Example Request
curl "https://api.support-os.net/api/v1/tickets?view=to_do&assignee=unassigned&safety=true&limit=50" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."Example Response
{
"data": [
{
"id": "3a2b1c0d-9e8f-7a6b-5c4d-3e2f1a0b9c8d",
"status": "escalated",
"priority": "critical",
"category": "abuse_harassment",
"subject": "User report: threatening messages",
"source": "webhook",
"createdAt": "2026-04-02T14:30:01.123Z",
"nextAction": "safety_review",
"nextActionReason": "Priority offence flagged: harassment",
"waitingSince": "2026-04-02T14:30:01.123Z",
"lastOutboundAt": null,
"assignee": null,
"slaDueAt": "2026-04-02T18:30:01.123Z",
"safetyFlag": "harassment",
"hasPendingDraft": false,
"resolvedBy": null,
"resolvedAt": null
}
],
"cursor": "eyJrIjpbMSwyLCIyMDI2LTA0LTAyVDE0OjMwOjAxLjEyM1oiXX0=",
"hasMore": true
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
| 400 | Invalid query parameters, an unknown category, or a malformed cursor or one from a different view. |
| 429 | Rate limit exceeded. |
/api/v1/tickets/countsBearer JWT (agent, safeguarding_lead or admin)Counts for the three inbox views (to_do, waiting, done) plus safety_to_do, the number of safety tickets in to_do. Every filter narrows these four counts. The legacy per-status keys (all, open, pending, pending_customer, pending_agent, resolved, escalated, referred_to_authority, auto_resolved and needs_reply) are still returned and ignore the filters. Merged tickets are never counted.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| assignee | string | optional | me, unassigned, or a user ID. |
| safety | boolean | optional | Count only safety tickets. |
| draftReady | boolean | optional | Count only tickets with a fresh pending AI draft. |
| priority | string | optional | low, medium, high or critical. |
| category | string | optional | Ticket category. |
| search | string | optional | Same matching as the list endpoint. |
Example Request
curl "https://api.support-os.net/api/v1/tickets/counts?assignee=me" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."Example Response
{
"data": {
"to_do": 14,
"waiting": 6,
"done": 212,
"safety_to_do": 2,
"all": 232,
"open": 5,
"pending_agent": 7,
"escalated": 2,
"pending_customer": 6,
"resolved": 210,
"referred_to_authority": 2
}
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
| 400 | Invalid query parameters. |
/api/v1/tickets/:id/assignBearer JWT (agent, safeguarding_lead or admin)Assign a ticket to a user, or unassign it with userId null. Agents may assign a ticket to themselves or unassign themselves; admins and safeguarding leads may assign anyone in the tenant. The assignee must be an active user of the tenant. Every change is recorded on the ticket timeline as a note_added event with data { action: assigned or unassigned, from, to, fromName, toName, actorId }. Assigning the current assignee again returns 200 and records nothing. Returns the updated ticket in the list row format, whichever view it is in. To assign many tickets, use bulk-update with assignedAgentId.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string (UUID) | required | Ticket ID. |
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| userId | string (UUID) | null | required | The user to assign, or null to unassign. |
Example Request
curl -X POST https://api.support-os.net/api/v1/tickets/3a2b1c0d-9e8f-7a6b-5c4d-3e2f1a0b9c8d/assign \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{ "userId": "8c7d6e5f-4a3b-2c1d-0e9f-8a7b6c5d4e3f" }'Example Response
{
"data": {
"id": "3a2b1c0d-9e8f-7a6b-5c4d-3e2f1a0b9c8d",
"status": "pending_agent",
"nextAction": "customer_replied",
"assignee": { "id": "8c7d6e5f-4a3b-2c1d-0e9f-8a7b6c5d4e3f", "name": "Sam Patel" }
}
}Error Responses
| Status | Description |
|---|---|
| 400 | Invalid body, or the user is not an active user of this tenant. |
| 401 | Missing or invalid JWT. |
| 403 | An agent tried to assign someone else, or to unassign another person's ticket. |
| 404 | Ticket not found or not accessible by this tenant. |
/api/v1/users/assignableBearer JWT (agent, safeguarding_lead or admin)Active, non-deleted users of the tenant who can be assigned tickets, sorted by name.
Example Request
curl https://api.support-os.net/api/v1/users/assignable \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."Example Response
{
"data": [
{ "id": "8c7d6e5f-4a3b-2c1d-0e9f-8a7b6c5d4e3f", "name": "Sam Patel", "role": "agent" },
{ "id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d", "name": "Jo Reid", "role": "safeguarding_lead" }
]
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
/api/v1/tickets/:idBearer JWTGet full details for a single ticket including AI classification, timeline events, and linked safety cases. The inbox fields (nextAction, assignee and so on) are returned only by the list and assign endpoints.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string (UUID) | required | Ticket ID. |
Example Request
curl https://api.support-os.net/api/v1/tickets/tkt_3a2b1c0d-9e8f-7a6b-5c4d-3e2f1a0b9c8d \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."Example Response
{
"id": "tkt_3a2b1c0d-9e8f-7a6b-5c4d-3e2f1a0b9c8d",
"tenant_id": "ten_f1e2d3c4-b5a6-9788-7c6d-5e4f3a2b1c0d",
"status": "open",
"priority": "high",
"category": "harassment",
"subject": "User report: inappropriate messages",
"source": "webhook",
"event_id": "evt_9f8e7d6c-5b4a-3c2d-1e0f-a1b2c3d4e5f6",
"ai_classification": {
"category": "harassment",
"confidence": 0.92,
"model": "claude-sonnet-4-20250514",
"provider": "anthropic",
"risk_indicators": ["threatening_language", "repeated_contact"]
},
"assigned_to": null,
"safety_case_id": null,
"timeline": [
{
"id": "evt_c1d2e3f4-a5b6-7c8d-9e0f-a1b2c3d4e5f6",
"type": "ticket_created",
"actor": "system",
"timestamp": "2026-04-02T14:30:01.123Z",
"details": { "source": "webhook" }
},
{
"id": "evt_d2e3f4a5-b6c7-8d9e-0f1a-b2c3d4e5f6a7",
"type": "ai_classified",
"actor": "system",
"timestamp": "2026-04-02T14:30:02.456Z",
"details": { "category": "harassment", "confidence": 0.92 }
}
],
"created_at": "2026-04-02T14:30:01.123Z",
"updated_at": "2026-04-02T14:30:02.456Z"
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
| 404 | Ticket not found or not accessible by this tenant. |
/api/v1/tickets/:idBearer JWT (agent, safeguarding_lead or admin)Change a ticket's status, category or priority. Moving a ticket into csea_concern starts the mandatory CSEA workflow: the ticket is escalated at critical priority, flagged csea, and a safety case, evidence preservation and SLA timers follow. Moving a ticket out of csea_concern is allowed only for a safeguarding lead with a reason; it is recorded as their confirmation that the ticket is not CSEA and leaves the safety case, evidence, referrals and SLA timers untouched. Any request that sends category, even the ticket's current one, confirms it: a low-confidence triage review (nextAction check_category) is cleared.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string (UUID) | required | Ticket ID. |
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| status | string | optional | New status. When moving into csea_concern, omit it or send escalated. |
| category | string | optional | New category. |
| priority | string | optional | New priority. When moving into csea_concern, omit it or send critical. |
| reason | string | optional | Up to 2,000 characters. Required when a safeguarding lead moves a ticket out of csea_concern. |
Example Request
curl -X PATCH https://api.support-os.net/api/v1/tickets/tkt_3a2b1c0d-9e8f-7a6b-5c4d-3e2f1a0b9c8d \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"category": "abuse_harassment",
"reason": "Reviewed the reported messages: adult harassment, no child involved"
}'Example Response
{
"data": {
"id": "tkt_3a2b1c0d-9e8f-7a6b-5c4d-3e2f1a0b9c8d",
"status": "escalated",
"priority": "critical",
"category": "abuse_harassment",
"safetyCaseId": "case_5e6f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9a0b",
"updatedAt": "2026-04-02T15:02:44.210Z"
}
}Error Responses
| Status | Description |
|---|---|
| 400 | Invalid body; a move into csea_concern that also sends a status other than escalated or a priority other than critical; or a safeguarding lead moving a ticket out of csea_concern without a reason. |
| 401 | Missing or invalid JWT. |
| 403 | Only a safeguarding lead can move a ticket out of csea_concern. |
| 404 | Ticket not found or not accessible by this tenant. |
/api/v1/tickets/bulk-updateBearer JWT (agent, safeguarding_lead or admin)Update up to 100 tickets at once, including assignment via assignedAgentId (null unassigns). Tickets moved into csea_concern each start the mandatory CSEA workflow. Assignment follows the single-ticket assign rules: the assignee must be an active user of the tenant, the stored name comes from that user, an agent may only assign themselves, and an agent unassigning a ticket that belongs to someone else skips that ticket. Each change records the same timeline event as the assign endpoint. Sending category clears a low-confidence triage review, as PATCH does. Status changes skip safety-flagged and merged tickets; each skipped ticket is listed in details with its reason. Tickets already in csea_concern cannot be moved out of it in bulk: the whole request is refused and a safeguarding lead must use PATCH /api/v1/tickets/:id with a reason for each ticket.
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| ticketIds | string[] (UUID) | required | 1–100 ticket IDs. |
| updates | object | required | Any of status, category, priority and assignedAgentId (nullable). assignedAgentName is accepted but ignored. |
Example Request
curl -X POST https://api.support-os.net/api/v1/tickets/bulk-update \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"ticketIds": ["tkt_3a2b1c0d-9e8f-7a6b-5c4d-3e2f1a0b9c8d"],
"updates": { "priority": "high" }
}'Example Response
{
"data": {
"processed": 1,
"skipped": 0,
"failed": 0,
"details": [
{ "ticketId": "tkt_3a2b1c0d-9e8f-7a6b-5c4d-3e2f1a0b9c8d", "status": "success" }
]
}
}Error Responses
| Status | Description |
|---|---|
| 400 | Invalid body; an assignee who is not an active user of this tenant; or a category change that would move a csea_concern ticket out of it (error.details.ticketIds lists those tickets). |
| 401 | Missing or invalid JWT. |
| 403 | An agent tried to assign the tickets to someone else. |
/api/v1/tickets/:id/draftsBearer JWTGenerate an AI draft response for a ticket. The draft uses tenant-configured prompts and relevant knowledge base articles.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string (UUID) | required | Ticket ID. |
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| tone | string | optional | Tone override: professional, empathetic, firm. Defaults to tenant config. |
| instructions | string | optional | Additional instructions for the AI when generating the draft. |
Example Request
curl -X POST https://api.support-os.net/api/v1/tickets/tkt_3a2b1c0d-9e8f-7a6b-5c4d-3e2f1a0b9c8d/drafts \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"tone": "empathetic",
"instructions": "Reference our community guidelines"
}'Example Response
{
"id": "drf_a1b2c3d4-e5f6-7a8b-9c0d-e1f2a3b4c5d6",
"ticket_id": "tkt_3a2b1c0d-9e8f-7a6b-5c4d-3e2f1a0b9c8d",
"content": "Hi there,\n\nThank you for reaching out to us about this. We take reports of harassment very seriously and I want you to know we are looking into this.\n\nOur Community Guidelines clearly state that threatening or intimidating behaviour is not tolerated. We have flagged the reported user for review by our safety team.\n\nWe will update you once our investigation is complete. In the meantime, you can block this user from your settings.\n\nBest regards,\nDating Frame Support",
"confidence": 0.88,
"model": "claude-sonnet-4-20250514",
"provider": "anthropic",
"tokens_used": 342,
"knowledge_articles_used": [
{ "id": "ka_b1c2d3e4", "title": "Community Guidelines — Harassment Policy" }
],
"status": "pending_approval",
"created_at": "2026-04-02T14:35:12.789Z"
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
| 404 | Ticket not found. |
| 422 | Ticket is closed or already has a pending draft. |
| 429 | Rate limit exceeded. |
/api/v1/tickets/:id/drafts/:draftId/approveBearer JWTApprove an AI-generated draft response. The response is sent to the user and the ticket status is updated.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string (UUID) | required | Ticket ID. |
| draftId | string (UUID) | required | Draft ID. |
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| edits | string | optional | Edited content to use instead of the original draft. If omitted, the original draft is sent as-is. |
Example Request
curl -X POST https://api.support-os.net/api/v1/tickets/tkt_3a2b1c0d-9e8f-7a6b-5c4d-3e2f1a0b9c8d/drafts/drf_a1b2c3d4-e5f6-7a8b-9c0d-e1f2a3b4c5d6/approve \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{}'Example Response
{
"id": "drf_a1b2c3d4-e5f6-7a8b-9c0d-e1f2a3b4c5d6",
"status": "approved",
"approved_by": "usr_c4d5e6f7-a8b9-0c1d-2e3f-4a5b6c7d8e9f",
"approved_at": "2026-04-02T14:40:05.321Z",
"ticket_status": "resolved"
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
| 404 | Ticket or draft not found. |
| 409 | Draft has already been approved or rejected. |
Safety Cases
Safety cases track safety investigations including CSEA, harassment, threats, and other priority offences. Cases follow a strict status workflow and may trigger mandatory NCA referrals.
/api/v1/casesBearer JWTList safety cases for the current tenant. Supports filtering by status and case type.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| status | string | optional | Filter by status: open, investigating, escalated, referred, closed. |
| caseType | string | optional | Filter by type: csea, harassment, threats, fraud, self_harm, other. |
| cursor | string | optional | Cursor for pagination. |
| limit | number | optional | Results per page (1–100, default 25). |
Example Request
curl https://api.support-os.net/api/v1/cases?status=open&caseType=csea&limit=10 \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."Example Response
{
"data": [
{
"id": "cas_e1f2a3b4-c5d6-7e8f-9a0b-c1d2e3f4a5b6",
"tenant_id": "ten_f1e2d3c4-b5a6-9788-7c6d-5e4f3a2b1c0d",
"case_type": "csea",
"status": "open",
"priority": "critical",
"subject": "Potential CSEA — hash match detected",
"ticket_ids": ["tkt_3a2b1c0d-9e8f-7a6b-5c4d-3e2f1a0b9c8d"],
"assigned_to": "usr_c4d5e6f7-a8b9-0c1d-2e3f-4a5b6c7d8e9f",
"sla_deadline": "2026-04-02T15:30:00.000Z",
"evidence_count": 3,
"referral_id": null,
"created_at": "2026-04-02T14:30:05.000Z",
"updated_at": "2026-04-02T14:30:05.000Z"
}
],
"pagination": {
"cursor": "eyJpZCI6ImNhc19lMWYyYTNiNCJ9",
"has_more": false,
"total": 1
}
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
| 400 | Invalid query parameters. |
| 429 | Rate limit exceeded. |
/api/v1/cases/:idBearer JWTGet full details for a safety case including evidence, timeline, and referral status.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string (UUID) | required | Safety case ID. |
Example Request
curl https://api.support-os.net/api/v1/cases/cas_e1f2a3b4-c5d6-7e8f-9a0b-c1d2e3f4a5b6 \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."Example Response
{
"id": "cas_e1f2a3b4-c5d6-7e8f-9a0b-c1d2e3f4a5b6",
"tenant_id": "ten_f1e2d3c4-b5a6-9788-7c6d-5e4f3a2b1c0d",
"case_type": "csea",
"status": "investigating",
"priority": "critical",
"subject": "Potential CSEA — hash match detected",
"description": "PhotoDNA hash match on uploaded image. Automatic evidence preservation triggered.",
"ticket_ids": ["tkt_3a2b1c0d-9e8f-7a6b-5c4d-3e2f1a0b9c8d"],
"assigned_to": "usr_c4d5e6f7-a8b9-0c1d-2e3f-4a5b6c7d8e9f",
"sla_deadline": "2026-04-02T15:30:00.000Z",
"evidence": [
{
"id": "evi_f1a2b3c4-d5e6-7f8a-9b0c-d1e2f3a4b5c6",
"type": "image_hash",
"preserved_at": "2026-04-02T14:30:03.000Z",
"s3_object_lock": true
}
],
"referral": null,
"timeline": [
{
"type": "case_created",
"actor": "system",
"timestamp": "2026-04-02T14:30:05.000Z"
},
{
"type": "evidence_preserved",
"actor": "system",
"timestamp": "2026-04-02T14:30:06.000Z"
}
],
"created_at": "2026-04-02T14:30:05.000Z",
"updated_at": "2026-04-02T14:32:10.000Z"
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
| 404 | Case not found or not accessible by this tenant. |
/api/v1/cases/:id/statusBearer JWTTransition a safety case to a new status. Status transitions are validated — for example, CSEA cases cannot be closed without a referral.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string (UUID) | required | Safety case ID. |
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| status | string | required | New status: investigating, escalated, referred, closed. |
| reason | string | required | Reason for the status change (logged to audit trail). |
Example Request
curl -X PATCH https://api.support-os.net/api/v1/cases/cas_e1f2a3b4-c5d6-7e8f-9a0b-c1d2e3f4a5b6/status \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"status": "escalated",
"reason": "Confirmed CSEA material — escalating to safeguarding lead"
}'Example Response
{
"id": "cas_e1f2a3b4-c5d6-7e8f-9a0b-c1d2e3f4a5b6",
"status": "escalated",
"previous_status": "investigating",
"updated_at": "2026-04-02T14:45:00.000Z"
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
| 404 | Case not found. |
| 422 | Invalid status transition (e.g. closing a CSEA case without referral). |
Evidence
Evidence is immutable content preserved in S3 with Object Lock (Compliance Mode). Evidence cannot be modified or deleted. Every access is logged to the chain-of-custody audit trail.
/api/v1/evidenceBearer JWTList evidence items. Must filter by case_id. Every call to this endpoint is logged to the evidence access audit trail.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| case_id | string (UUID) | required | Safety case ID to list evidence for. |
| cursor | string | optional | Cursor for pagination. |
| limit | number | optional | Results per page (1–100, default 25). |
Example Request
curl https://api.support-os.net/api/v1/evidence?case_id=cas_e1f2a3b4-c5d6-7e8f-9a0b-c1d2e3f4a5b6 \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."Example Response
{
"data": [
{
"id": "evi_f1a2b3c4-d5e6-7f8a-9b0c-d1e2f3a4b5c6",
"case_id": "cas_e1f2a3b4-c5d6-7e8f-9a0b-c1d2e3f4a5b6",
"type": "image_hash",
"description": "PhotoDNA hash match — original image preserved",
"content_type": "image/jpeg",
"size_bytes": 245760,
"hash_sha256": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
"s3_key": "evidence/ten_f1e2d3c4/cas_e1f2a3b4/evi_f1a2b3c4.enc",
"object_lock_mode": "COMPLIANCE",
"object_lock_retain_until": "2033-04-02T14:30:03.000Z",
"preserved_at": "2026-04-02T14:30:03.000Z",
"preserved_by": "system",
"created_at": "2026-04-02T14:30:03.000Z"
}
],
"pagination": {
"cursor": null,
"has_more": false,
"total": 1
}
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
| 400 | Missing required case_id parameter. |
| 404 | Case not found or not accessible by this tenant. |
Referrals
NCA (National Crime Agency) referrals for CSEA and other priority offences. Referrals are generated from safety cases and follow a strict workflow. Once submitted, referrals cannot be modified.
/api/v1/referralsBearer JWTList referrals for the current tenant.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| status | string | optional | Filter by status: draft, pending_review, submitted, acknowledged. |
| cursor | string | optional | Cursor for pagination. |
| limit | number | optional | Results per page (1–100, default 25). |
Example Request
curl https://api.support-os.net/api/v1/referrals?status=submitted&limit=10 \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."Example Response
{
"data": [
{
"id": "ref_b1c2d3e4-f5a6-7b8c-9d0e-f1a2b3c4d5e6",
"case_id": "cas_e1f2a3b4-c5d6-7e8f-9a0b-c1d2e3f4a5b6",
"type": "nca",
"status": "submitted",
"submitted_at": "2026-04-02T15:00:00.000Z",
"submitted_by": "usr_c4d5e6f7-a8b9-0c1d-2e3f-4a5b6c7d8e9f",
"reference_number": "NCA-2026-04-0042",
"evidence_count": 3,
"created_at": "2026-04-02T14:50:00.000Z"
}
],
"pagination": {
"cursor": null,
"has_more": false,
"total": 1
}
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
| 429 | Rate limit exceeded. |
/api/v1/cases/:caseId/referralsBearer JWTCreate a referral from a safety case. For CSEA cases, the system auto-attaches all preserved evidence. The referral enters draft status and must be reviewed before submission.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| caseId | string (UUID) | required | Safety case ID. |
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| type | string | required | Referral type: nca, police, other. |
| urgency | string | required | Urgency level: routine, urgent, emergency. |
| summary | string | required | Human-written summary of the referral. |
| contact_details | object | optional | Override contact details for the referral recipient. |
Example Request
curl -X POST https://api.support-os.net/api/v1/cases/cas_e1f2a3b4-c5d6-7e8f-9a0b-c1d2e3f4a5b6/referrals \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"type": "nca",
"urgency": "urgent",
"summary": "CSEA material detected via PhotoDNA hash match. User account suspended. Evidence preserved with full chain of custody."
}'Example Response
{
"id": "ref_b1c2d3e4-f5a6-7b8c-9d0e-f1a2b3c4d5e6",
"case_id": "cas_e1f2a3b4-c5d6-7e8f-9a0b-c1d2e3f4a5b6",
"type": "nca",
"status": "draft",
"urgency": "urgent",
"summary": "CSEA material detected via PhotoDNA hash match. User account suspended. Evidence preserved with full chain of custody.",
"evidence_count": 3,
"created_at": "2026-04-02T14:50:00.000Z"
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
| 404 | Case not found. |
| 409 | Case already has an active referral. |
| 422 | Case does not have sufficient evidence for a referral. |
Compliance
Compliance endpoints provide an overview of your regulatory status and generate Ofcom transparency reports as required by the UK Online Safety Act.
/api/v1/compliance/summaryBearer JWTGet a summary of compliance status including SLA adherence, open cases, pending referrals, and upcoming deadlines.
Example Request
curl https://api.support-os.net/api/v1/compliance/summary \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."Example Response
{
"tenant_id": "ten_f1e2d3c4-b5a6-9788-7c6d-5e4f3a2b1c0d",
"period": {
"from": "2026-03-01T00:00:00.000Z",
"to": "2026-04-02T23:59:59.999Z"
},
"sla_adherence": {
"overall": 0.94,
"csea_response": 1.0,
"priority_offence": 0.97,
"standard_ticket": 0.91
},
"open_cases": {
"total": 4,
"csea": 1,
"priority_offence": 2,
"other": 1
},
"pending_referrals": 1,
"overdue_items": [
{
"type": "ticket",
"id": "tkt_7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d",
"sla_deadline": "2026-04-02T12:00:00.000Z",
"overdue_by_minutes": 150
}
],
"next_transparency_report_due": "2026-06-30T23:59:59.999Z"
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
/api/v1/compliance/transparency-reportBearer JWTGenerate an Ofcom transparency report for the specified period. Returns structured report data.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| from | string (ISO 8601) | required | Start of reporting period. |
| to | string (ISO 8601) | required | End of reporting period. |
Example Request
curl "https://api.support-os.net/api/v1/compliance/transparency-report?from=2026-01-01T00:00:00Z&to=2026-03-31T23:59:59Z" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."Example Response
{
"report_id": "rpt_a1b2c3d4-e5f6-7a8b-9c0d-e1f2a3b4c5d6",
"tenant_id": "ten_f1e2d3c4-b5a6-9788-7c6d-5e4f3a2b1c0d",
"period": {
"from": "2026-01-01T00:00:00.000Z",
"to": "2026-03-31T23:59:59.000Z"
},
"summary": {
"total_reports_received": 1247,
"total_actions_taken": 892,
"total_cases_opened": 156,
"total_referrals_made": 3,
"average_response_time_hours": 2.4,
"sla_adherence_rate": 0.94
},
"categories": {
"csea": { "reports": 8, "cases": 5, "referrals": 3, "actions": 8 },
"harassment": { "reports": 412, "cases": 67, "referrals": 0, "actions": 298 },
"threats": { "reports": 89, "cases": 23, "referrals": 0, "actions": 64 },
"fraud": { "reports": 201, "cases": 34, "referrals": 0, "actions": 178 },
"self_harm": { "reports": 45, "cases": 12, "referrals": 0, "actions": 39 },
"other": { "reports": 492, "cases": 15, "referrals": 0, "actions": 305 }
},
"generated_at": "2026-04-02T15:00:00.000Z"
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
| 400 | Invalid or missing date range parameters. |
/api/v1/compliance/transparency-report/exportBearer JWTExport a transparency report as JSON or CSV. Returns a download URL.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| from | string (ISO 8601) | required | Start of reporting period. |
| to | string (ISO 8601) | required | End of reporting period. |
| format | string | required | Export format: json or csv. |
Example Request
curl "https://api.support-os.net/api/v1/compliance/transparency-report/export?from=2026-01-01T00:00:00Z&to=2026-03-31T23:59:59Z&format=csv" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."Example Response
{
"download_url": "https://supportos-exports.s3.eu-west-2.amazonaws.com/exports/ten_f1e2d3c4/rpt_a1b2c3d4.csv?X-Amz-Expires=3600&...",
"format": "csv",
"expires_at": "2026-04-02T16:00:00.000Z",
"size_bytes": 15420
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
| 400 | Invalid date range or unsupported format. |
Workflows
Configure automated workflows that trigger on specific conditions (e.g. ticket created with category X, safety case escalated). Workflows consist of trigger conditions and ordered steps.
/api/v1/workflowsBearer JWTList all workflows for the current tenant.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| active | boolean | optional | Filter by active/inactive status. |
| cursor | string | optional | Cursor for pagination. |
| limit | number | optional | Results per page (1–100, default 25). |
Example Request
curl https://api.support-os.net/api/v1/workflows?active=true \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."Example Response
{
"data": [
{
"id": "wfl_a1b2c3d4-e5f6-7a8b-9c0d-e1f2a3b4c5d6",
"name": "Auto-escalate CSEA tickets",
"description": "Automatically creates safety case and escalates when CSEA is detected",
"active": true,
"trigger": {
"event": "ticket.classified",
"conditions": { "category": "csea" }
},
"steps": [
{ "order": 1, "action": "create_safety_case", "config": { "case_type": "csea", "priority": "critical" } },
{ "order": 2, "action": "preserve_evidence", "config": {} },
{ "order": 3, "action": "assign_to_role", "config": { "role": "safeguarding_lead" } },
{ "order": 4, "action": "send_notification", "config": { "channel": "urgent", "template": "csea_alert" } }
],
"executions_count": 5,
"last_executed_at": "2026-04-01T09:15:00.000Z",
"created_at": "2026-03-01T10:00:00.000Z",
"updated_at": "2026-03-15T14:20:00.000Z"
}
],
"pagination": {
"cursor": null,
"has_more": false,
"total": 1
}
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
/api/v1/workflowsBearer JWTCreate a new workflow. CSEA mandatory workflows cannot be created or modified by tenants.
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | required | Workflow name. |
| description | string | optional | Workflow description. |
| trigger | object | required | Trigger configuration: { event: string, conditions: object }. |
| steps | array | required | Ordered array of steps: [{ order: number, action: string, config: object }]. |
| active | boolean | optional | Whether to activate immediately (default: false). |
Example Request
curl -X POST https://api.support-os.net/api/v1/workflows \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"name": "High-priority ticket assignment",
"description": "Auto-assign high priority tickets to senior agents",
"trigger": {
"event": "ticket.classified",
"conditions": { "priority": "high" }
},
"steps": [
{ "order": 1, "action": "assign_to_role", "config": { "role": "senior_agent" } },
{ "order": 2, "action": "send_notification", "config": { "channel": "slack", "template": "high_priority_ticket" } }
],
"active": true
}'Example Response
{
"id": "wfl_d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a",
"name": "High-priority ticket assignment",
"active": true,
"trigger": {
"event": "ticket.classified",
"conditions": { "priority": "high" }
},
"steps": [
{ "order": 1, "action": "assign_to_role", "config": { "role": "senior_agent" } },
{ "order": 2, "action": "send_notification", "config": { "channel": "slack", "template": "high_priority_ticket" } }
],
"created_at": "2026-04-02T15:10:00.000Z"
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
| 400 | Invalid workflow definition — missing trigger or steps. |
| 422 | Workflow conflicts with a mandatory system workflow. |
/api/v1/workflows/:idBearer JWTUpdate an existing workflow. Only non-mandatory workflows can be modified.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string (UUID) | required | Workflow ID. |
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | optional | Updated name. |
| description | string | optional | Updated description. |
| trigger | object | optional | Updated trigger configuration. |
| steps | array | optional | Updated steps array. |
| active | boolean | optional | Enable or disable the workflow. |
Example Request
curl -X PATCH https://api.support-os.net/api/v1/workflows/wfl_d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"active": false
}'Example Response
{
"id": "wfl_d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a",
"name": "High-priority ticket assignment",
"active": false,
"updated_at": "2026-04-02T15:15:00.000Z"
}Error Responses
| Status | Description |
|---|---|
| 400 | Turning on a workflow whose saved steps are in a format that is no longer supported, without sending replacement steps. error.details.steps lists each step that needs updating. |
| 401 | Missing or invalid JWT. |
| 404 | Workflow not found. |
| 403 | Cannot modify a mandatory system workflow. |
/api/v1/workflows/:idBearer JWTSoft-delete a workflow. Mandatory system workflows cannot be deleted.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string (UUID) | required | Workflow ID. |
Example Request
curl -X DELETE https://api.support-os.net/api/v1/workflows/wfl_d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."Example Response
{
"id": "wfl_d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a",
"deleted": true,
"deleted_at": "2026-04-02T15:20:00.000Z"
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
| 404 | Workflow not found. |
| 403 | Cannot delete a mandatory system workflow. |
Connectors
Connectors integrate external platforms (email providers, chat systems, CRMs) with SupportOS. Each connector has inbound mappings (how external data maps to SupportOS entities) and outbound actions (what actions SupportOS can push back to the external platform).
/api/v1/connectorsBearer JWTList all connectors for the current tenant.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| status | string | optional | Filter by status: active, inactive, error. |
| cursor | string | optional | Cursor for pagination. |
| limit | number | optional | Results per page (1–100, default 25). |
Example Request
curl https://api.support-os.net/api/v1/connectors \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."Example Response
{
"data": [
{
"id": "con_a1b2c3d4-e5f6-7a8b-9c0d-e1f2a3b4c5d6",
"name": "Dating Frame Production",
"type": "webhook",
"status": "active",
"config": {
"webhook_url": "https://api.support-os.net/api/v1/webhooks/events",
"signing_secret_last4": "f6a7"
},
"inbound_mappings_count": 3,
"outbound_actions_count": 2,
"last_event_at": "2026-04-02T14:30:01.000Z",
"created_at": "2026-03-01T10:00:00.000Z"
}
],
"pagination": {
"cursor": null,
"has_more": false,
"total": 1
}
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
/api/v1/connectorsBearer JWTCreate a new connector. A webhook signing secret is generated and returned once — store it securely.
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | required | Display name for the connector. |
| type | string | required | Connector type: webhook, email, api. |
| config | object | optional | Type-specific configuration (e.g. SMTP settings for email). |
Example Request
curl -X POST https://api.support-os.net/api/v1/connectors \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"name": "Dating Frame Staging",
"type": "webhook"
}'Example Response
{
"id": "con_e5f6a7b8-c9d0-1e2f-3a4b-5c6d7e8f9a0b",
"name": "Dating Frame Staging",
"type": "webhook",
"status": "active",
"config": {
"webhook_url": "https://api.support-os.net/api/v1/webhooks/events",
"signing_secret": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
},
"created_at": "2026-04-02T15:25:00.000Z"
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
| 400 | Invalid connector configuration. |
/api/v1/connectors/:id/mappingsBearer JWTCreate an inbound field mapping for a connector. Maps external event fields to SupportOS ticket fields.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string (UUID) | required | Connector ID. |
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| event_type | string | required | External event type this mapping applies to. |
| field_mappings | array | required | Array of { source: string, target: string, transform?: string } mappings. |
Example Request
curl -X POST https://api.support-os.net/api/v1/connectors/con_a1b2c3d4-e5f6-7a8b-9c0d-e1f2a3b4c5d6/mappings \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"event_type": "user_reported",
"field_mappings": [
{ "source": "payload.reporter_id", "target": "reporter_external_id" },
{ "source": "payload.reported_user_id", "target": "subject_external_id" },
{ "source": "payload.reason", "target": "category", "transform": "map_category" }
]
}'Example Response
{
"id": "map_c1d2e3f4-a5b6-7c8d-9e0f-a1b2c3d4e5f6",
"connector_id": "con_a1b2c3d4-e5f6-7a8b-9c0d-e1f2a3b4c5d6",
"event_type": "user_reported",
"field_mappings": [
{ "source": "payload.reporter_id", "target": "reporter_external_id" },
{ "source": "payload.reported_user_id", "target": "subject_external_id" },
{ "source": "payload.reason", "target": "category", "transform": "map_category" }
],
"created_at": "2026-04-02T15:30:00.000Z"
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
| 404 | Connector not found. |
| 400 | Invalid field mapping — unknown target field or transform. |
/api/v1/connectors/:id/actionsBearer JWTCreate an outbound action for a connector. Defines what actions SupportOS can push to the external platform.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string (UUID) | required | Connector ID. |
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| action_type | string | required | Action type: suspend_user, remove_content, send_warning, restore_content. |
| endpoint | string | required | URL to call when this action is triggered. |
| method | string | required | HTTP method: POST, PUT, PATCH, DELETE. |
| headers | object | optional | Additional headers to include in the request. |
| payload_template | string | required | JSON template with {{variable}} placeholders for the request body. |
Example Request
curl -X POST https://api.support-os.net/api/v1/connectors/con_a1b2c3d4-e5f6-7a8b-9c0d-e1f2a3b4c5d6/actions \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"action_type": "suspend_user",
"endpoint": "https://api.datingframe.com/v2/users/{{user_id}}/suspend",
"method": "POST",
"headers": { "X-Service-Key": "{{service_key}}" },
"payload_template": "{ \"reason\": \"{{reason}}\", \"duration\": \"{{duration}}\" }"
}'Example Response
{
"id": "act_d1e2f3a4-b5c6-7d8e-9f0a-b1c2d3e4f5a6",
"connector_id": "con_a1b2c3d4-e5f6-7a8b-9c0d-e1f2a3b4c5d6",
"action_type": "suspend_user",
"endpoint": "https://api.datingframe.com/v2/users/{{user_id}}/suspend",
"method": "POST",
"created_at": "2026-04-02T15:35:00.000Z"
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
| 404 | Connector not found. |
| 400 | Invalid action configuration — missing endpoint or payload_template. |
Knowledge Base
Manage knowledge base articles used by AI to generate accurate draft responses. Articles are embedded as vectors using pgvector for semantic search. Up to 5 relevant articles are injected into AI context per query.
/api/v1/knowledgeBearer JWTList knowledge base articles for the current tenant.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| category | string | optional | Filter by category. |
| status | string | optional | Filter by status: published, draft, archived. |
| cursor | string | optional | Cursor for pagination. |
| limit | number | optional | Results per page (1–100, default 25). |
Example Request
curl https://api.support-os.net/api/v1/knowledge?status=published&limit=10 \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."Example Response
{
"data": [
{
"id": "ka_b1c2d3e4-f5a6-7b8c-9d0e-f1a2b3c4d5e6",
"title": "Community Guidelines — Harassment Policy",
"category": "policies",
"status": "published",
"content_preview": "Our platform has a zero-tolerance policy for harassment...",
"word_count": 1240,
"embedding_status": "indexed",
"last_used_at": "2026-04-02T14:35:12.000Z",
"created_at": "2026-02-15T10:00:00.000Z",
"updated_at": "2026-03-20T14:30:00.000Z"
}
],
"pagination": {
"cursor": null,
"has_more": false,
"total": 1
}
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
/api/v1/knowledgeBearer JWTCreate a new knowledge base article. The article is automatically embedded for semantic search after creation.
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| title | string | required | Article title. |
| content | string | required | Article content in plain text or Markdown. |
| category | string | required | Article category (e.g. policies, procedures, faq). |
| tags | string[] | optional | Tags for filtering and organisation. |
| status | string | optional | Initial status: draft or published (default: draft). |
Example Request
curl -X POST https://api.support-os.net/api/v1/knowledge \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"title": "Account Deletion Process",
"content": "When a user requests account deletion, the following steps must be followed:\n\n1. Verify the user identity via email confirmation.\n2. Initiate a 30-day cooling-off period.\n3. After 30 days, permanently delete all personal data.\n4. Retain anonymised usage data for compliance purposes.",
"category": "procedures",
"tags": ["gdpr", "account", "deletion"],
"status": "published"
}'Example Response
{
"id": "ka_e5f6a7b8-c9d0-1e2f-3a4b-5c6d7e8f9a0b",
"title": "Account Deletion Process",
"category": "procedures",
"status": "published",
"tags": ["gdpr", "account", "deletion"],
"word_count": 52,
"embedding_status": "pending",
"created_at": "2026-04-02T15:40:00.000Z"
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
| 400 | Missing required fields (title, content, or category). |
/api/v1/knowledge/searchBearer JWTSemantic search across knowledge base articles using pgvector. Returns the top matching articles ranked by relevance.
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| query | string | required | Natural language search query. |
| limit | number | optional | Max results to return (1–10, default 5). |
| category | string | optional | Restrict search to a specific category. |
| threshold | number | optional | Minimum similarity score (0–1, default 0.5). |
Example Request
curl -X POST https://api.support-os.net/api/v1/knowledge/search \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"query": "What is our harassment policy?",
"limit": 3
}'Example Response
{
"results": [
{
"id": "ka_b1c2d3e4-f5a6-7b8c-9d0e-f1a2b3c4d5e6",
"title": "Community Guidelines — Harassment Policy",
"category": "policies",
"content_preview": "Our platform has a zero-tolerance policy for harassment...",
"similarity": 0.94,
"word_count": 1240
},
{
"id": "ka_c2d3e4f5-a6b7-8c9d-0e1f-a2b3c4d5e6f7",
"title": "Escalation Procedures for Repeat Offenders",
"category": "procedures",
"content_preview": "When a user has multiple harassment reports...",
"similarity": 0.78,
"word_count": 890
}
],
"query_embedding_tokens": 12
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
| 400 | Missing search query. |
Patterns
AI-detected patterns across tickets and safety cases. Patterns surface recurring issues, emerging threats, and anomalies that may require attention. Patterns can be confirmed or dismissed by operators.
/api/v1/patternsBearer JWTList detected patterns for the current tenant.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| status | string | optional | Filter by status: detected, confirmed, dismissed. |
| severity | string | optional | Filter by severity: low, medium, high, critical. |
| cursor | string | optional | Cursor for pagination. |
| limit | number | optional | Results per page (1–100, default 25). |
Example Request
curl https://api.support-os.net/api/v1/patterns?status=detected&severity=high \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."Example Response
{
"data": [
{
"id": "pat_a1b2c3d4-e5f6-7a8b-9c0d-e1f2a3b4c5d6",
"title": "Surge in harassment reports from London region",
"description": "42% increase in harassment reports originating from users in the London area over the past 7 days, compared to the 30-day average.",
"severity": "high",
"status": "detected",
"category": "harassment",
"affected_tickets": 23,
"confidence": 0.87,
"detected_at": "2026-04-02T06:00:00.000Z",
"time_range": {
"from": "2026-03-26T00:00:00.000Z",
"to": "2026-04-02T00:00:00.000Z"
},
"created_at": "2026-04-02T06:00:00.000Z"
}
],
"pagination": {
"cursor": null,
"has_more": false,
"total": 1
}
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
/api/v1/patterns/:id/reviewBearer JWTConfirm or dismiss a detected pattern. Confirmed patterns remain visible and may trigger workflows. Dismissed patterns are hidden from the default view.
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | string (UUID) | required | Pattern ID. |
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| action | string | required | Review action: confirm or dismiss. |
| notes | string | optional | Optional notes about the review decision. |
Example Request
curl -X PATCH https://api.support-os.net/api/v1/patterns/pat_a1b2c3d4-e5f6-7a8b-9c0d-e1f2a3b4c5d6/review \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"action": "confirm",
"notes": "Correlates with a viral social media post targeting our users. Increasing moderation capacity."
}'Example Response
{
"id": "pat_a1b2c3d4-e5f6-7a8b-9c0d-e1f2a3b4c5d6",
"status": "confirmed",
"reviewed_by": "usr_c4d5e6f7-a8b9-0c1d-2e3f-4a5b6c7d8e9f",
"reviewed_at": "2026-04-02T15:50:00.000Z",
"notes": "Correlates with a viral social media post targeting our users. Increasing moderation capacity."
}Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid JWT. |
| 404 | Pattern not found. |
| 400 | Invalid action — must be 'confirm' or 'dismiss'. |
User Reports
Public-facing endpoint for the user reporting widget. Allows end-users of tenant platforms to submit reports directly. Authenticated via an API key (x-api-key header), not a user JWT.
/api/v1/reportsAPI Key (x-api-key header)Submit a user report from the embedded reporting widget or a server-to-server integration. Creates a ticket in the tenant's queue. Authenticated via the x-api-key header, which identifies the tenant. Accepts flexible field names: 'category' or 'report_category', 'message' or 'description'.
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| category | string | required | Report category. Accepts full names (general_support, billing_refund, login_account, abuse_harassment, etc.) or shorthand aliases (account, billing, support, abuse, scam, etc.). Unrecognised values default to 'other'. Alias: report_category. |
| message | string | required | The customer's message body (1–10,000 characters). Displayed in the conversation thread on the ticket. Alias: description. |
| subject | string | optional | Custom subject line (max 500 characters). If omitted, auto-generated from the category. |
| reporter_user_id | string | optional | External user ID of the reporter. Stored as the ticket's user identifier. |
| reported_user_id | string | optional | External user ID of the reported user (for abuse/safety reports). |
| evidence_urls | string[] | optional | URLs of evidence related to this report (max 20, must be valid URLs). |
| metadata | object | optional | Arbitrary key-value metadata. Common fields like member_name, member_email, member_type, page_url, and user_agent are automatically mapped to the Customer card in the dashboard. Include a context_panels array for structured sidebar cards (see below). |
| metadata.context_panels | ContextPanel[] | optional | Array of structured data panels to display in the ticket sidebar. Each panel has a key (stable identifier), label (display title), and fields array. See example below. |
Example Request
curl -X POST https://api.support-os.net/api/v1/reports \
-H "Content-Type: application/json" \
-H "x-api-key: sos_your_api_key_here" \
-d '{
"category": "billing",
"subject": "Payment not received",
"message": "I have not been paid for my last campaign. Please investigate.",
"reporter_user_id": "7061",
"metadata": {
"member_name": "Jane Doe",
"member_email": "jane@example.com",
"member_type": "blogger",
"member_company": "Jane Writes",
"page_url": "https://app.example.com/contact-support",
"user_agent": "Mozilla/5.0 ...",
"context_panels": [
{
"key": "linked_websites",
"label": "Linked Websites",
"fields": [
{ "key": "primary_url", "label": "Primary URL", "value": "https://janewrites.com", "type": "link" },
{ "key": "verified", "label": "Verified", "value": true, "type": "boolean" }
]
},
{
"key": "active_campaigns",
"label": "Active Campaigns",
"fields": [
{ "key": "campaign_1", "label": "Summer Fashion Collab", "value": "In Progress", "type": "badge" },
{ "key": "campaign_2", "label": "Tech Review Q2", "value": "Completed", "type": "badge" }
]
},
{
"key": "account_details",
"label": "Account Details",
"fields": [
{ "key": "signup_date", "label": "Signed Up", "value": "2025-01-15", "type": "date" },
{ "key": "plan", "label": "Plan", "value": "Premium", "type": "text" },
{ "key": "lifetime_earnings", "label": "Lifetime Earnings", "value": 1240.50, "type": "currency" }
]
}
]
}
}'Example Response
{
"report_id": "782ed880-8a6d-4e6d-a50c-cafb418aeebd",
"status": "received",
"message": "Your report has been received and will be reviewed by our team."
}Error Responses
| Status | Description |
|---|---|
| 400 | Missing or invalid required fields. Returns field-level errors with a hint showing accepted field names and aliases. |
| 401 | Missing or invalid API key (x-api-key header). |
| 429 | Rate limit exceeded — max 10 reports per minute per IP. |
Context Panels
Context panels let you send structured data sections alongside tickets that display as sidebar cards in the ticket detail view. They are sent inside the metadata.context_panels array of a report. Each panel is self-describing — no configuration is needed for them to render. Tenant admins can customise display labels, ordering, and visibility from Settings → Context Panels.
Panel Structure
Each panel object has three fields: key (a stable identifier used for config matching), label (the display title shown in the sidebar), and fields (an array of field objects).
Field Types
Each field has key, label, value, and an optional type. Supported types: text (default), link (clickable URL), email (mailto link), boolean (Yes/No badge), date (formatted date), currency (locale-formatted with £ symbol), badge (styled tag), code (monospace font).
Automatic Customer Card Mapping
The following top-level metadata fields are automatically mapped to the Customer sidebar card: member_name / name / customer_name → Customer Name, member_email / email / customer_email → Customer Email, member_type / account_type → Account Type, member_company / company / site_name → Site Name, page_url / site_url → Site URL, member_id / user_id → User ID (also used as reporter_user_id fallback), user_agent → User Agent.
Category Aliases
The category field accepts shorthand aliases: account → login_account, billing → billing_refund, refund → billing_refund, support → general_support, general → general_support, abuse → abuse_harassment, scam → scam_fraud, fraud → scam_fraud, moderation → moderation_query, csea → csea_concern, underage → underage_risk, hate → hate_speech, suicide → suicide_self_harm. Unrecognised values default to 'other'.