API documentation sample

Process security issue tickets from external systems

The SecurityTicketRequestProcessor API accepts reports from third-party scanners, bug bounty platforms, CI pipelines, and partner tools, then creates and manages tickets inside Security Tickets.

Base URL
https://api.securitytickets.example.com/v1

Sandbox URL
https://sandbox.securitytickets.example.com/v1

What you can do

  • Create, update, list, and delete security tickets
  • Add comments, evidence, and attachments
  • Run bulk updates across multiple tickets
  • Subscribe to webhook events for ticket lifecycle changes
  • Track audit events for compliance and reporting

Quickstart request

curl --request POST \
  --url https://sandbox.securitytickets.example.com/v1/tickets \
  --header 'Authorization: Bearer <access_token>' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: 5f7c6f2d-60c4-43b8-a2af-9c1da1bda111' \
  --data '{
    "title": "SQL injection in login endpoint",
    "description": "Unsanitised input reaches the auth query.",
    "severity": "high",
    "source": "partner_scanner",
    "reporter": {
      "name": "Acme Security Scanner",
      "external_reference": "scan-49302"
    }
  }'

Authentication

The API uses OAuth 2.0 Client Credentials. Each integration receives a client ID and client secret. Exchange these for a short-lived bearer token.

Token endpoint

POST /oauth2/token

curl --request POST \
  --url https://auth.securitytickets.example.com/oauth2/token \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data 'grant_type=client_credentials&client_id=st_client_123&client_secret=st_secret_456&scope=tickets:write tickets:read webhooks:read'

Response

{
  "access_token": "eyJhbGciOi...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "tickets:write tickets:read webhooks:read"
}

Scopes

ScopeDescription
tickets:readRead ticket data and ticket-related resources
tickets:writeCreate, update, and delete tickets
comments:writeCreate and delete comments
attachments:writeUpload or remove attachments
bulk:writePerform bulk actions
webhooks:readView webhook delivery and subscription metadata

Request and response conventions

Headers

  • Authorization: Bearer <token>
  • Content-Type: application/json
  • Accept: application/json
  • Idempotency-Key required for POST /tickets and bulk write operations
  • X-Correlation-ID optional for tracing across systems

Pagination

Collection endpoints use cursor pagination.

GET /tickets?limit=2&after=cur_01J93Y6Q5K6M3

{
  "data": [...],
  "page": {
    "next_cursor": "cur_01J93Y7WQF8K9",
    "previous_cursor": "cur_01J93Y4BMT1QA",
    "has_more": true,
    "limit": 2
  }
}

Tickets API

POST/tickets

Create a new security ticket.

Request body

{
  "title": "Authentication bypass via debug flag",
  "description": "The debug=true query parameter bypasses MFA in staging-like environments.",
  "severity": "critical",
  "priority": "p1",
  "category": "authentication",
  "source": "bug_bounty_platform",
  "affected_product": "customer-portal",
  "affected_versions": ["4.9.1", "4.9.2"],
  "reporter": {
    "name": "Northwind Security Research",
    "email": "alerts@northwind.example",
    "external_reference": "bb-11402"
  },
  "metadata": {
    "environment": "production",
    "scanner_confidence": "high"
  }
}

201 Created

{
  "id": "tkt_01J93YT9A6V2T4X5J2S9GQ6KWR",
  "title": "Authentication bypass via debug flag",
  "status": "open",
  "severity": "critical",
  "priority": "p1",
  "created_at": "2026-04-22T09:12:44Z",
  "updated_at": "2026-04-22T09:12:44Z",
  "links": {
    "self": "/v1/tickets/tkt_01J93YT9A6V2T4X5J2S9GQ6KWR",
    "comments": "/v1/tickets/tkt_01J93YT9A6V2T4X5J2S9GQ6KWR/comments",
    "attachments": "/v1/tickets/tkt_01J93YT9A6V2T4X5J2S9GQ6KWR/attachments"
  }
}
GET/tickets/{ticket_id}

Retrieve a single ticket, including assignment and workflow details.

GET/tickets

List tickets with filtering, sorting, and cursor pagination.

Query parameters

NameTypeDescription
statusstringFilter by open, triaged, in_progress, resolved, or closed
severitystringFilter by low, medium, high, critical
sourcestringFilter by reporting system
updated_afterdatetimeISO 8601 timestamp
limitinteger1–100, default 25
afterstringCursor for next page
sortstringcreated_at, -created_at, severity, -updated_at

Example

GET /tickets?status=open&severity=high&limit=2&sort=-updated_at
PATCH/tickets/{ticket_id}

Partially update mutable fields on an existing ticket.

{
  "status": "in_progress",
  "assigned_team": "application-security",
  "priority": "p2"
}
DELETE/tickets/{ticket_id}

Delete a ticket. This performs a soft delete and returns 204 No Content.

Comments API

GET/tickets/{ticket_id}/comments

List comments for a ticket.

POST/tickets/{ticket_id}/comments

Add an internal or external comment.

{
  "body": "The owning team has confirmed the issue and deployed a mitigation.",
  "visibility": "internal"
}
DELETE/tickets/{ticket_id}/comments/{comment_id}

Delete a comment by ID.

Attachments API

POST/tickets/{ticket_id}/attachments

Attach supporting evidence such as logs, screenshots, or proof-of-concept files.

{
  "filename": "repro-steps.txt",
  "content_type": "text/plain",
  "content_base64": "U3RlcCAxOiBPcGVuIC9sb2dpbi4uLg=="
}
GET/tickets/{ticket_id}/attachments

List attachment metadata for a ticket.

DELETE/tickets/{ticket_id}/attachments/{attachment_id}

Delete an attachment.

Bulk operations

POST/bulk/tickets/update

Apply the same update to multiple tickets in a single request.

{
  "ticket_ids": [
    "tkt_01J93YT9A6V2T4X5J2S9GQ6KWR",
    "tkt_01J93YV57RHSPM1KQ3W4EZ2NRD"
  ],
  "changes": {
    "status": "triaged",
    "assigned_team": "psirt"
  }
}

Bulk writes are asynchronous. The API returns 202 Accepted and a job resource.

GET/bulk/jobs/{job_id}

Retrieve status for a bulk operation, including per-ticket results.

Audit events

GET/audit-events

List auditable actions across tickets, comments, attachments, and webhook administration.

GET /audit-events?resource_type=ticket&resource_id=tkt_01J93YT9A6V2T4X5J2S9GQ6KWR&limit=50

Webhooks

Webhooks let your system receive near real-time notifications when ticket state changes occur. Configure webhook destinations in the Security Tickets admin console.

Supported events

  • ticket.created
  • ticket.updated
  • ticket.deleted
  • ticket.comment.created
  • ticket.attachment.created
  • bulk.job.completed

Delivery headers

  • X-ST-Event: Event name
  • X-ST-Delivery-ID: Unique delivery identifier
  • X-ST-Signature-256: HMAC SHA-256 signature
  • X-ST-Timestamp: Unix timestamp for replay protection

Example payload

{
  "id": "evt_01J940MBCG8JVD9Y6VB4T0ZZYQ",
  "type": "ticket.updated",
  "created_at": "2026-04-22T11:01:02Z",
  "data": {
    "ticket_id": "tkt_01J93YT9A6V2T4X5J2S9GQ6KWR",
    "status": "resolved",
    "severity": "critical",
    "updated_by": "system"
  }
}

Signature verification

signed_payload = timestamp + "." + raw_request_body
expected_signature = HMAC_SHA256(webhook_secret, signed_payload)

Return any 2xx status code to acknowledge receipt. Failed deliveries are retried with exponential backoff for up to 24 hours.

Error handling

Error shape

{
  "error": {
    "code": "validation_error",
    "message": "severity must be one of: low, medium, high, critical",
    "request_id": "req_01J940Y0K1N0Y1XK2QZ8J4D3YB",
    "details": [
      {
        "field": "severity",
        "issue": "invalid_enum_value"
      }
    ]
  }
}

Common status codes

StatusMeaning
400Malformed request
401Missing or invalid token
403Valid token, insufficient scope
404Requested resource not found
409Conflict, often due to idempotency reuse with different payload
422Validation failed
429Rate limit exceeded
500Unexpected server error

Rate limits and idempotency

Rate limits

  • Default: 1,000 requests per hour per client
  • Burst: 60 requests per minute
  • Bulk endpoints: 100 requests per hour

Responses include:

  • X-RateLimit-Limit
  • X-RateLimit-Remaining
  • X-RateLimit-Reset

Idempotency

To prevent duplicate ticket creation, send a unique Idempotency-Key header with write requests. Keys are retained for 24 hours.

Versioning and changelog

The API is versioned in the URL, for example /v1.

VersionStatusNotes
v1CurrentInitial public version with tickets, comments, attachments, bulk jobs, and webhooks
v0DeprecatedInternal beta used for early partner testing