SupportOS
Sign in

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`.

POST/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

NameTypeRequiredDescription
event_idstring (UUID)requiredUnique identifier for this event — used for idempotency.
event_typestringrequiredOne of: user_reported, message_flagged, csam_hash_match, account_suspended, content_removed, user_blocked, appeal_submitted.
timestampstring (ISO 8601)requiredWhen the event occurred on the source platform.
sourcestringrequiredIdentifier for the source platform (e.g. 'dating-frame').
payloadobjectrequiredEvent-specific data. Schema varies by event_type.
payload.reporter_idstringoptionalID of the reporting user (user_reported events).
payload.reported_user_idstringoptionalID of the reported user.
payload.reasonstringoptionalReason for the report.
payload.contentobjectoptionalContent associated with the event (messages, images, etc.).
payload.hash_valuestringoptionalHash value for csam_hash_match events.
payload.hash_algorithmstringoptionalHash 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

StatusDescription
400Invalid payload — missing required fields or unknown event_type.
401Missing or invalid API key.
403HMAC signature verification failed.
409Event with this event_id has already been processed (idempotent — safe to retry).
429Rate 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.

GET/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

NameTypeRequiredDescription
viewstringoptionalto_do, waiting or done. When present, status, needsReply and autoResolved are ignored.
assigneestringoptionalme, unassigned, or a user ID (UUID). View only.
safetybooleanoptionalWhen 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.
draftReadybooleanoptionalWhen true, only tickets with a pending AI draft created after the latest inbound message. View only.
resolvedBystringoptionalai or human. Applied only when view=done; ignored otherwise.
prioritystringoptionalFilter by priority: low, medium, high, critical.
categorystringoptionalFilter by category. Must be a valid ticket category.
searchstringoptionalUp to 200 characters. Matches subject, body, userIdentifier and customer email.
includeMergedbooleanoptionalWhen true, merged tickets are included. They are hidden by default.
statusstringoptionalLegacy. Filter by status: open, pending_agent, pending_customer, escalated, resolved, referred_to_authority. Ignored when view is present.
needsReplybooleanoptionalLegacy. When true, return only open or pending_agent tickets. Prefer view=to_do.
cursorstringoptionalOpaque cursor from the previous page, valid only for the view that returned it. Omit for the first page.
limitnumberoptionalNumber 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

StatusDescription
401Missing or invalid JWT.
400Invalid query parameters, an unknown category, or a malformed cursor or one from a different view.
429Rate limit exceeded.
GET/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

NameTypeRequiredDescription
assigneestringoptionalme, unassigned, or a user ID.
safetybooleanoptionalCount only safety tickets.
draftReadybooleanoptionalCount only tickets with a fresh pending AI draft.
prioritystringoptionallow, medium, high or critical.
categorystringoptionalTicket category.
searchstringoptionalSame 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

StatusDescription
401Missing or invalid JWT.
400Invalid query parameters.
POST/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

NameTypeRequiredDescription
idstring (UUID)requiredTicket ID.

Body Parameters

NameTypeRequiredDescription
userIdstring (UUID) | nullrequiredThe 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

StatusDescription
400Invalid body, or the user is not an active user of this tenant.
401Missing or invalid JWT.
403An agent tried to assign someone else, or to unassign another person's ticket.
404Ticket not found or not accessible by this tenant.
GET/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

StatusDescription
401Missing or invalid JWT.
GET/api/v1/tickets/:idBearer JWT

Get 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

NameTypeRequiredDescription
idstring (UUID)requiredTicket 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

StatusDescription
401Missing or invalid JWT.
404Ticket not found or not accessible by this tenant.
PATCH/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

NameTypeRequiredDescription
idstring (UUID)requiredTicket ID.

Body Parameters

NameTypeRequiredDescription
statusstringoptionalNew status. When moving into csea_concern, omit it or send escalated.
categorystringoptionalNew category.
prioritystringoptionalNew priority. When moving into csea_concern, omit it or send critical.
reasonstringoptionalUp 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

StatusDescription
400Invalid 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.
401Missing or invalid JWT.
403Only a safeguarding lead can move a ticket out of csea_concern.
404Ticket not found or not accessible by this tenant.
POST/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

NameTypeRequiredDescription
ticketIdsstring[] (UUID)required1–100 ticket IDs.
updatesobjectrequiredAny 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

StatusDescription
400Invalid 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).
401Missing or invalid JWT.
403An agent tried to assign the tickets to someone else.
POST/api/v1/tickets/:id/draftsBearer JWT

Generate an AI draft response for a ticket. The draft uses tenant-configured prompts and relevant knowledge base articles.

Path Parameters

NameTypeRequiredDescription
idstring (UUID)requiredTicket ID.

Body Parameters

NameTypeRequiredDescription
tonestringoptionalTone override: professional, empathetic, firm. Defaults to tenant config.
instructionsstringoptionalAdditional 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

StatusDescription
401Missing or invalid JWT.
404Ticket not found.
422Ticket is closed or already has a pending draft.
429Rate limit exceeded.
POST/api/v1/tickets/:id/drafts/:draftId/approveBearer JWT

Approve an AI-generated draft response. The response is sent to the user and the ticket status is updated.

Path Parameters

NameTypeRequiredDescription
idstring (UUID)requiredTicket ID.
draftIdstring (UUID)requiredDraft ID.

Body Parameters

NameTypeRequiredDescription
editsstringoptionalEdited 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

StatusDescription
401Missing or invalid JWT.
404Ticket or draft not found.
409Draft 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.

GET/api/v1/casesBearer JWT

List safety cases for the current tenant. Supports filtering by status and case type.

Query Parameters

NameTypeRequiredDescription
statusstringoptionalFilter by status: open, investigating, escalated, referred, closed.
caseTypestringoptionalFilter by type: csea, harassment, threats, fraud, self_harm, other.
cursorstringoptionalCursor for pagination.
limitnumberoptionalResults 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

StatusDescription
401Missing or invalid JWT.
400Invalid query parameters.
429Rate limit exceeded.
GET/api/v1/cases/:idBearer JWT

Get full details for a safety case including evidence, timeline, and referral status.

Path Parameters

NameTypeRequiredDescription
idstring (UUID)requiredSafety 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

StatusDescription
401Missing or invalid JWT.
404Case not found or not accessible by this tenant.
PATCH/api/v1/cases/:id/statusBearer JWT

Transition a safety case to a new status. Status transitions are validated — for example, CSEA cases cannot be closed without a referral.

Path Parameters

NameTypeRequiredDescription
idstring (UUID)requiredSafety case ID.

Body Parameters

NameTypeRequiredDescription
statusstringrequiredNew status: investigating, escalated, referred, closed.
reasonstringrequiredReason 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

StatusDescription
401Missing or invalid JWT.
404Case not found.
422Invalid 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.

GET/api/v1/evidenceBearer JWT

List evidence items. Must filter by case_id. Every call to this endpoint is logged to the evidence access audit trail.

Query Parameters

NameTypeRequiredDescription
case_idstring (UUID)requiredSafety case ID to list evidence for.
cursorstringoptionalCursor for pagination.
limitnumberoptionalResults 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

StatusDescription
401Missing or invalid JWT.
400Missing required case_id parameter.
404Case 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.

GET/api/v1/referralsBearer JWT

List referrals for the current tenant.

Query Parameters

NameTypeRequiredDescription
statusstringoptionalFilter by status: draft, pending_review, submitted, acknowledged.
cursorstringoptionalCursor for pagination.
limitnumberoptionalResults 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

StatusDescription
401Missing or invalid JWT.
429Rate limit exceeded.
POST/api/v1/cases/:caseId/referralsBearer JWT

Create 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

NameTypeRequiredDescription
caseIdstring (UUID)requiredSafety case ID.

Body Parameters

NameTypeRequiredDescription
typestringrequiredReferral type: nca, police, other.
urgencystringrequiredUrgency level: routine, urgent, emergency.
summarystringrequiredHuman-written summary of the referral.
contact_detailsobjectoptionalOverride 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

StatusDescription
401Missing or invalid JWT.
404Case not found.
409Case already has an active referral.
422Case 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.

GET/api/v1/compliance/summaryBearer JWT

Get 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

StatusDescription
401Missing or invalid JWT.
GET/api/v1/compliance/transparency-reportBearer JWT

Generate an Ofcom transparency report for the specified period. Returns structured report data.

Query Parameters

NameTypeRequiredDescription
fromstring (ISO 8601)requiredStart of reporting period.
tostring (ISO 8601)requiredEnd 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

StatusDescription
401Missing or invalid JWT.
400Invalid or missing date range parameters.
GET/api/v1/compliance/transparency-report/exportBearer JWT

Export a transparency report as JSON or CSV. Returns a download URL.

Query Parameters

NameTypeRequiredDescription
fromstring (ISO 8601)requiredStart of reporting period.
tostring (ISO 8601)requiredEnd of reporting period.
formatstringrequiredExport 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

StatusDescription
401Missing or invalid JWT.
400Invalid 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.

GET/api/v1/workflowsBearer JWT

List all workflows for the current tenant.

Query Parameters

NameTypeRequiredDescription
activebooleanoptionalFilter by active/inactive status.
cursorstringoptionalCursor for pagination.
limitnumberoptionalResults 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

StatusDescription
401Missing or invalid JWT.
POST/api/v1/workflowsBearer JWT

Create a new workflow. CSEA mandatory workflows cannot be created or modified by tenants.

Body Parameters

NameTypeRequiredDescription
namestringrequiredWorkflow name.
descriptionstringoptionalWorkflow description.
triggerobjectrequiredTrigger configuration: { event: string, conditions: object }.
stepsarrayrequiredOrdered array of steps: [{ order: number, action: string, config: object }].
activebooleanoptionalWhether 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

StatusDescription
401Missing or invalid JWT.
400Invalid workflow definition — missing trigger or steps.
422Workflow conflicts with a mandatory system workflow.
PATCH/api/v1/workflows/:idBearer JWT

Update an existing workflow. Only non-mandatory workflows can be modified.

Path Parameters

NameTypeRequiredDescription
idstring (UUID)requiredWorkflow ID.

Body Parameters

NameTypeRequiredDescription
namestringoptionalUpdated name.
descriptionstringoptionalUpdated description.
triggerobjectoptionalUpdated trigger configuration.
stepsarrayoptionalUpdated steps array.
activebooleanoptionalEnable 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

StatusDescription
400Turning 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.
401Missing or invalid JWT.
404Workflow not found.
403Cannot modify a mandatory system workflow.
DELETE/api/v1/workflows/:idBearer JWT

Soft-delete a workflow. Mandatory system workflows cannot be deleted.

Path Parameters

NameTypeRequiredDescription
idstring (UUID)requiredWorkflow 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

StatusDescription
401Missing or invalid JWT.
404Workflow not found.
403Cannot 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).

GET/api/v1/connectorsBearer JWT

List all connectors for the current tenant.

Query Parameters

NameTypeRequiredDescription
statusstringoptionalFilter by status: active, inactive, error.
cursorstringoptionalCursor for pagination.
limitnumberoptionalResults 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

StatusDescription
401Missing or invalid JWT.
POST/api/v1/connectorsBearer JWT

Create a new connector. A webhook signing secret is generated and returned once — store it securely.

Body Parameters

NameTypeRequiredDescription
namestringrequiredDisplay name for the connector.
typestringrequiredConnector type: webhook, email, api.
configobjectoptionalType-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

StatusDescription
401Missing or invalid JWT.
400Invalid connector configuration.
POST/api/v1/connectors/:id/mappingsBearer JWT

Create an inbound field mapping for a connector. Maps external event fields to SupportOS ticket fields.

Path Parameters

NameTypeRequiredDescription
idstring (UUID)requiredConnector ID.

Body Parameters

NameTypeRequiredDescription
event_typestringrequiredExternal event type this mapping applies to.
field_mappingsarrayrequiredArray 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

StatusDescription
401Missing or invalid JWT.
404Connector not found.
400Invalid field mapping — unknown target field or transform.
POST/api/v1/connectors/:id/actionsBearer JWT

Create an outbound action for a connector. Defines what actions SupportOS can push to the external platform.

Path Parameters

NameTypeRequiredDescription
idstring (UUID)requiredConnector ID.

Body Parameters

NameTypeRequiredDescription
action_typestringrequiredAction type: suspend_user, remove_content, send_warning, restore_content.
endpointstringrequiredURL to call when this action is triggered.
methodstringrequiredHTTP method: POST, PUT, PATCH, DELETE.
headersobjectoptionalAdditional headers to include in the request.
payload_templatestringrequiredJSON 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

StatusDescription
401Missing or invalid JWT.
404Connector not found.
400Invalid 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.

GET/api/v1/knowledgeBearer JWT

List knowledge base articles for the current tenant.

Query Parameters

NameTypeRequiredDescription
categorystringoptionalFilter by category.
statusstringoptionalFilter by status: published, draft, archived.
cursorstringoptionalCursor for pagination.
limitnumberoptionalResults 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

StatusDescription
401Missing or invalid JWT.
POST/api/v1/knowledgeBearer JWT

Create a new knowledge base article. The article is automatically embedded for semantic search after creation.

Body Parameters

NameTypeRequiredDescription
titlestringrequiredArticle title.
contentstringrequiredArticle content in plain text or Markdown.
categorystringrequiredArticle category (e.g. policies, procedures, faq).
tagsstring[]optionalTags for filtering and organisation.
statusstringoptionalInitial 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

StatusDescription
401Missing or invalid JWT.
400Missing required fields (title, content, or category).
POST/api/v1/knowledge/searchBearer JWT

Semantic search across knowledge base articles using pgvector. Returns the top matching articles ranked by relevance.

Body Parameters

NameTypeRequiredDescription
querystringrequiredNatural language search query.
limitnumberoptionalMax results to return (1–10, default 5).
categorystringoptionalRestrict search to a specific category.
thresholdnumberoptionalMinimum 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

StatusDescription
401Missing or invalid JWT.
400Missing 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.

GET/api/v1/patternsBearer JWT

List detected patterns for the current tenant.

Query Parameters

NameTypeRequiredDescription
statusstringoptionalFilter by status: detected, confirmed, dismissed.
severitystringoptionalFilter by severity: low, medium, high, critical.
cursorstringoptionalCursor for pagination.
limitnumberoptionalResults 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

StatusDescription
401Missing or invalid JWT.
PATCH/api/v1/patterns/:id/reviewBearer JWT

Confirm or dismiss a detected pattern. Confirmed patterns remain visible and may trigger workflows. Dismissed patterns are hidden from the default view.

Path Parameters

NameTypeRequiredDescription
idstring (UUID)requiredPattern ID.

Body Parameters

NameTypeRequiredDescription
actionstringrequiredReview action: confirm or dismiss.
notesstringoptionalOptional 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

StatusDescription
401Missing or invalid JWT.
404Pattern not found.
400Invalid 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.

POST/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

NameTypeRequiredDescription
categorystringrequiredReport 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.
messagestringrequiredThe customer's message body (1–10,000 characters). Displayed in the conversation thread on the ticket. Alias: description.
subjectstringoptionalCustom subject line (max 500 characters). If omitted, auto-generated from the category.
reporter_user_idstringoptionalExternal user ID of the reporter. Stored as the ticket's user identifier.
reported_user_idstringoptionalExternal user ID of the reported user (for abuse/safety reports).
evidence_urlsstring[]optionalURLs of evidence related to this report (max 20, must be valid URLs).
metadataobjectoptionalArbitrary 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_panelsContextPanel[]optionalArray 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

StatusDescription
400Missing or invalid required fields. Returns field-level errors with a hint showing accepted field names and aliases.
401Missing or invalid API key (x-api-key header).
429Rate 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'.