API Reference

Integrate your Kora workspace data into dashboards, automations, and internal tools with the Kora Public API.

v1StableRESTJSONAPI Key Auth

What's new

NEWTicket Merging, Bulk Actions & Realtime Comments

JUL 2026Public API — Members & Webhooks endpoints now available

Overview

The Kora Public API is a workspace-scoped REST API that gives programmatic access to tickets, comments, and analytics. All requests are authenticated via API key headers and return JSON.

Format

JSON

Version

v1

Auth

API Key (headers)

Quick Start

Make your first request in 30 seconds. Replace ws_abc123 and sk_xyz456 with your workspace ID and secret key from Settings → API Access.

curl https://api.usekora.app/public/v1/tickets \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456" \
  -G \
  --data-urlencode "start_date=2026-06-01" \
  --data-urlencode "end_date=2026-06-30"

That's it. You'll get a JSON response with your workspace's tickets. Explore the endpoints below for more.

Base URL

https://api.usekora.app/public/v1

All endpoints are prefixed with /public/v1. The Kora Dashboard API (used internally by the app) is separate and not accessible via Public API keys.

Authentication

Every request must include two headers identifying your workspace and proving your identity.

HeaderRequiredDescription
X-Workspace-IDrequiredYour workspace's unique identifier. Find it in Settings → API inside the Kora app.
X-Secret-KeyrequiredYour workspace's secret API key. Treat this like a password — never expose it in client-side code.
# Example request with auth headers
curl https://api.usekora.app/public/v1/tickets \
  -H "X-Workspace-ID: ws_your_workspace_id" \
  -H "X-Secret-Key: sk_your_secret_key" \
  -G \
  --data-urlencode "start_date=2026-06-01" \
  --data-urlencode "end_date=2026-06-30"

Keep your X-Secret-Key private. If compromised, regenerate it from Settings → API in the Kora app.

Rate Limits

Rate limits are applied per workspace, across all Public API endpoints. Dashboard app usage does not count toward the limit.

PlanDaily limitGrace buffer
Free5,000 requests / day+5%
Growth50,000 requests / day+5%
CustomCustom (contact sales)+5%

Every response includes these headers so you can track your usage:

HeaderDescription
X-RateLimit-LimitYour plan's daily request cap
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the window resets

When the limit is exceeded, the API returns 429 Too Many Requests with a retryAfter field indicating when to retry.

Errors

All errors return a JSON body with success: false, a human-readable error string, and the HTTP code.

StatusMeaning
400Bad request — missing or invalid parameters
401Unauthorised — invalid or missing API key headers
404Not found — the requested resource doesn't exist in your workspace
429Rate limit exceeded — slow down and retry after the reset window
500Internal server error — contact support@usekora.app
// Error response shape
{
  "success": false,
  "error": "start_date and end_date are required",
  "code": 400
}
GET

/workspace

Get a summary of your workspace with 20 key configuration fields.

Returns workspace metadata, plan details, workflow configuration, limits, and live counts (members, tickets, webhooks). Useful for building custom dashboards or validating workspace configuration before performing operations.

Response Fields

FieldTypeDescription
workspace_idstringUnique workspace identifier (e.g. ws_abc123)
namestringWorkspace display name
logo_urlstring|nullWorkspace logo URL
planstringPlan tier: free, growth, or custom
statusstringWorkspace status: active, suspended, etc.
ticket_workflowstringWorkflow mode: claim_based, assignment_based, auto_claim
auto_close_modestringAuto-close mode: disabled, instant, delayed
auto_close_delay_daysintegerDays before auto-closing resolved tickets
ticket_history_daysintegerTicket retention period in days
max_membersintegerMaximum allowed members for the plan
max_file_size_mbintegerMaximum file size per upload in MB
file_storage_limit_mbnumberTotal file storage limit in MB
api_rate_limitstringAPI rate limit description (e.g. 5000/day)
ai_analyst_limitintegerAI Analyst monthly usage limit
members_countintegerCurrent active member count
tickets_countintegerTotal tickets in workspace
webhooks_countintegerActive webhooks count
created_atdatetimeWorkspace creation timestamp
activation_datedatetime|nullWhen the workspace was activated
trial_expiry_datedatetime|nullTrial expiry timestamp (if on trial)
curl -s "https://api.usekora.app/public/v1/workspace" \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456"

Response 200 OK

{
  "success": true,
  "workspace": {
    "workspace_id": "ws_abc123",
    "name": "Acme Inc",
    "logo_url": "https://r2.usekora.app/logos/ws_abc123_logo.png",
    "plan": "growth",
    "status": "active",
    "ticket_workflow": "claim_based",
    "auto_close_mode": "disabled",
    "auto_close_delay_days": 7,
    "ticket_history_days": 365,
    "max_members": 15,
    "max_file_size_mb": 30,
    "file_storage_limit_mb": 1024,
    "api_rate_limit": "5000/day",
    "ai_analyst_limit": 10,
    "members_count": 5,
    "tickets_count": 142,
    "webhooks_count": 3,
    "created_at": "2026-06-01 10:00:00",
    "activation_date": "2026-06-01 10:00:00",
    "trial_expiry_date": null
  }
}
GET

/tickets

List and filter tickets within a date range.

Returns a paginated list of tickets created within the specified date range. Maximum range is 92 days. Results are ordered newest first. Use page and limit query parameters for pagination (max 100 per page).

Query Parameters

ParameterTypeRequiredDescription
start_datestringrequiredStart of date range. Format: YYYY-MM-DD
end_datestringrequiredEnd of date range. Format: YYYY-MM-DD. Max 92 days from start_date.
reporter_emailstringoptionalFilter by the reporter's email address.
pageintegeroptionalPage number (default: 1).
limitintegeroptionalItems per page (default: 100, max: 100).

Request

curl https://api.usekora.app/public/v1/tickets \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456" \
  -G \
  --data-urlencode "start_date=2026-06-01" \
  --data-urlencode "end_date=2026-06-30"

Response

{
  "success": true,
  "workspace_id": "ws_abc123",
  "request_range": {
    "start_date": "2026-06-01",
    "end_date": "2026-06-30",
    "duration_days": 29
  },
  "tickets": [
    {
      "ticket_number": 42,
      "title": "Login page throws 500 on Safari",
      "description": "Reproducible on Safari 17.x — ...",
      "category": "Bug",
      "priority": "High",
      "status": "Open",
      "reporter": "John Doe",
      "assignee": "Jane Smith",
      "created_at": "2026-06-15T09:41:22Z",
      "resolved_at": null
    }
  ],
  "total_count": 1,
  "returned_count": 1,
  "pagination": {
    "page": 1,
    "limit": 100,
    "total": 1,
    "total_pages": 1,
    "has_next": false
  }
}
POST

/tickets

Create a new ticket in your workspace.

Creates a new ticket with status Open. The reporter_email must be an active workspace member.

Request Body

FieldTypeRequiredDescription
titlestringrequiredShort title for the ticket.
descriptionstringrequiredFull description of the issue.
categorystringrequiredCategory label, e.g. Bug, Feature Request, Incident. Free-form string.
prioritystringrequiredOne of: Low, Medium, High, Critical
reporter_emailstringrequiredEmail of the reporter. Must be an active workspace member.

Request

curl -X POST https://api.usekora.app/public/v1/tickets \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Checkout button unresponsive on mobile",
    "description": "Tapping the checkout button on iOS Safari does nothing.",
    "category": "Bug",
    "priority": "High",
    "reporter_email": "john@example.com"
  }'

Response 201 Created

{
  "success": true,
  "workspace_id": "ws_abc123",
  "ticket": {
    "ticket_number": 43,
    "title": "Checkout button unresponsive on mobile",
    "description": "Tapping the checkout button on iOS Safari does nothing.",
    "category": "Bug",
    "priority": "High",
    "status": "Open",
    "reporter": "John Doe",
    "assignee": null,
    "created_at": "2026-06-19T18:30:00Z"
  }
}
GET

/tickets/:ticket_id

Get a single ticket with full details and attachments.

Returns the full ticket details including reporter, assignee, and attachments. The :ticket_id is the ticket's numeric ticket_number.

Path Parameters

ParameterTypeRequiredDescription
ticket_idintegerrequiredThe ticket's numeric identifier (ticket_number), e.g. 42.

Request

curl https://api.usekora.app/public/v1/tickets/42 \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456"

Response

{
              "success": true,
              "workspace_id": "ws_abc123",
              "ticket": {
                "ticket_number": 42,
              "title": "Login page throws 500 on Safari",
              "description": "Reproducible on Safari 17.x — ...",
              "category": "Bug",
              "priority": "High",
              "status": "Open",
              "reporter": "John Doe",
              "assignee": "Jane Smith",
              "created_at": "2026-06-15T09:41:22Z",
              "resolved_at": null,
              "updated_at": "2026-06-15T09:41:22Z",
              "attachments": []
  }
}
PATCH

/tickets/:ticket_id

Partially update a ticket's status, priority, or content.

All fields are optional — only the fields you send are changed. Uses the ticket's ticket_number as the path parameter.

Status transitions are validated. You can only move to a status that is reachable from the current one. Invalid transitions return 400 with the list of allowed next statuses.

Status Transition Map

Current statusAllowed transitions
OpenAssigned, In Progress, Resolved, Closed
AssignedIn Progress, Waiting for User, Waiting for Team, Resolved, Closed
In ProgressWaiting for User, Waiting for Team, Resolved, Closed
Waiting for UserIn Progress, Resolved, Closed
Waiting for TeamIn Progress, Resolved, Closed
ResolvedClosed, Reopened
ClosedReopened
ReopenedAssigned, In Progress, Resolved, Closed

Request Body

FieldTypeRequiredDescription
statusstringoptionalNew status. Must be a valid transition from current status.
prioritystringoptionalOne of: Low, Medium, High, Critical
titlestringoptionalUpdated ticket title.
descriptionstringoptionalUpdated ticket description.
categorystringoptionalUpdated category label.
assignee_emailstringoptionalEmail of an active workspace member to assign.

Request

curl -X PATCH https://api.usekora.app/public/v1/tickets/43 \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "In Progress",
    "priority": "Critical",
    "assignee_email": "jane@example.com"
  }'

Response 200 OK

{
              "success": true,
              "workspace_id": "ws_abc123",
              "ticket": {
                "ticket_number": 43,
              "title": "Checkout button unresponsive on mobile",
              "description": "Tapping the checkout button on iOS Safari does nothing.",
              "category": "Bug",
              "priority": "Critical",
              "status": "In Progress",
              "reporter": "John Doe",
              "assignee": "Jane Smith",
              "created_at": "2026-06-19T18:30:00Z",
              "resolved_at": null,
              "updated_at": "2026-06-19T19:00:00Z"
  }
}
POST

/tickets/:ticket_id/claim

Claim a ticket on behalf of a workspace member.

Available only in claim_based and auto_claim workflows. Sets the ticket status to Assigned (claim_based) or In Progress (auto_claim).

Request Body

FieldTypeDescription
claimer_emailstringEmail of the member claiming the ticket (required)
curl -s -X POST "https://api.usekora.app/public/v1/tickets/42/claim" \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456" \
  -H "Content-Type: application/json" \
  -d '{"claimer_email": "agent@acme.com"}'

Response 200 OK

{
  "success": true,
  "ticket": {
    "ticket_number": 42,
    "title": "Login page crash",
    "status": "Assigned",
    "claimed_by": "Jane Smith",
    "claimed_at": "2026-08-22T10:30:00.000Z"
  }
}
POST

/tickets/:ticket_id/merge

Merge a ticket into another ticket. Moves comments, reactions, and attachments.

The source ticket is closed and linked to the target. All comments, reactions, and attachments are moved to the target ticket. This operation is irreversible.

Request Body

FieldTypeDescription
target_ticket_idintegerTicket number to merge into (required)
merged_by_emailstringEmail of the member performing the merge (required)
curl -s -X POST "https://api.usekora.app/public/v1/tickets/42/merge" \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456" \
  -H "Content-Type: application/json" \
  -d '{"target_ticket_id": 45, "merged_by_email": "admin@acme.com"}'

Response 200 OK

{
  "success": true,
  "source_ticket": { "ticket_number": 42, "title": "Login crash", "status": "Closed" },
  "target_ticket": { "ticket_number": 45, "title": "Auth issues", "status": "Open" },
  "merged_by": "Admin User"
}
POST

/tickets/bulk

Update status and/or assignee for multiple tickets at once.

Update up to 100 tickets in a single request. All ticket IDs must belong to the authenticated workspace. Returns the count of updated tickets and any IDs that were not found.

Request Body

FieldTypeDescription
ticket_idsinteger[]Array of ticket numbers (required, max 100)
statusstringNew status (optional)
assignee_emailstringEmail of the new assignee (optional)
curl -s -X POST "https://api.usekora.app/public/v1/tickets/bulk" \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456" \
  -H "Content-Type: application/json" \
  -d '{"ticket_ids": [1, 2, 3], "status": "Closed"}'

Response 200 OK

{
  "success": true,
  "updated_count": 3,
  "not_found": []
}
GET

/tickets/:ticket_id/comments

Fetch comments and attachments for a ticket.

Returns all comments (and their attachments) for a specific ticket, ordered oldest-first. The :ticket_id is the ticket's numeric ticket_number.

Path Parameters

ParameterTypeRequiredDescription
ticket_idintegerrequiredThe ticket's numeric identifier (ticket_number), e.g. 42.

Request

curl https://api.usekora.app/public/v1/tickets/42/comments \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456"

Response

{
              "success": true,
              "workspace_id": "ws_abc123",
              "ticket_id": 42,
              "comments": [
              {
                "id": 1,
              "content": "Reproduced locally. Digging into the auth middleware.",
              "parent_id": null,
              "created_at": "2026-06-15T10:05:00Z",
              "user": "Jane Smith",
              "attachments": [
              {
                "id": 1,
              "file_name": "screenshot.png",
              "file_type": "image/png",
              "file_size": 204800,
              "storage_url": "https://..."
        }
              ]
    }
              ],
              "total_count": 1
}
POST

/tickets/:ticket_id/comments

Add a comment to a ticket.

Creates a new comment on the specified ticket. The author_email must be an active workspace member. Supports threaded replies via parent_id.

Request Body

FieldTypeRequiredDescription
contentstringrequiredComment body text.
author_emailstringrequiredEmail of an active workspace member who is the comment author.
parent_idintegeroptionalID of the parent comment for threaded replies. null for top-level comments.

Request

curl -X POST https://api.usekora.app/public/v1/tickets/42/comments \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Fixed in PR #123. Awaiting review.",
    "author_email": "jane@example.com"
  }'

Response 201 Created

{
              "success": true,
              "workspace_id": "ws_abc123",
              "ticket_id": 42,
              "comment": {
                "id": 2,
              "content": "Fixed in PR #123. Awaiting review.",
              "user": "Jane Smith",
              "parent_id": null,
              "attachments": [],
              "created_at": "2026-06-15T10:30:00Z"
  }
}
GET

/tickets/:ticket_id/comments/since

Get comments created after a given timestamp — ideal for realtime polling.

Returns all comments on a ticket created after the since timestamp. Use this for incremental polling — call every 5\u201310 seconds to get new comments without refetching the entire thread.

Query Parameters

ParameterTypeDescription
sincedatetimeISO 8601 timestamp (required, e.g. 2026-01-01T00:00:00Z)
curl -s "https://api.usekora.app/public/v1/tickets/42/comments/since?since=2026-08-22T00:00:00Z" \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456"

Response 200 OK

{
  "success": true,
  "ticket_id": 42,
  "since": "2026-08-22T00:00:00Z",
  "comments": [
    { "id": 15, "content": "Updated the auth flow", "user": "Jane Smith", "created_at": "2026-08-22T10:30:00Z" }
  ],
  "total_count": 1
}
GET

/tickets/:ticket_id/attachments

List all file attachments for a ticket.

Returns all attachments uploaded to a specific ticket, sorted by upload time (newest first).

curl -s "https://api.usekora.app/public/v1/tickets/42/attachments" \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456"

Response 200 OK

{
  "success": true,
  "ticket_id": 42,
  "attachments": [
    {
      "id": 8,
      "file_name": "screenshot.png",
      "file_type": "image/png",
      "file_size": 245678,
      "storage_url": "https://r2.usekora.app/attachments/123_screenshot.png",
      "uploaded_at": "2026-08-22 10:30:00"
    }
  ],
  "total_count": 1
}
POST

/tickets/:ticket_id/attachments

Upload a file attachment to a ticket.

Uploads a file to Cloudflare R2 storage and links it to the ticket. Request must be multipart/form-data. File size is validated against the workspace plan limit.

Form Fields

FieldTypeDescription
filebinaryThe file to upload (required)
uploader_emailstringEmail of the member uploading the file (required)
curl -s -X POST "https://api.usekora.app/public/v1/tickets/42/attachments" \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456" \
  -F "file=@screenshot.png" \
  -F "uploader_email=agent@acme.com"

Response 201 Created

{
  "success": true,
  "ticket_id": 42,
  "attachment": {
    "id": 9,
    "file_name": "screenshot.png",
    "file_type": "image/png",
    "file_size": 245678,
    "storage_url": "https://r2.usekora.app/attachments/123_screenshot.png",
    "uploaded_by": "Jane Smith"
  }
}
GET

/dashboard/metrics

Monthly analytics for custom dashboards and reports.

Returns aggregated metrics and chart data for a given calendar month. Useful for building custom dashboards, weekly reports, or Slack digests.

Query Parameters

ParameterTypeRequiredDescription
monthstringrequiredCalendar month to query. Format: YYYY-MM, e.g. 2026-06.

Request

curl "https://api.usekora.app/public/v1/dashboard/metrics?month=2026-06" \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456"

Response

{
              "success": true,
              "workspace_id": "ws_abc123",
              "month": "2026-06",
              "metrics": {
                "tickets_created": 134,
              "tickets_open": 22,
              "tickets_closed": 112,
              "tickets_reopened": 5
  },
              "charts": {
                "status_distribution": {
                "Open": 18,
              "In Progress": 4,
              "Resolved": 107,
              "Closed": 5
    },
              "tickets_by_user": [
              {"user": "Alice", "count": 41 },
              {"user": "Bob", "count": 29 }
              ],
              "tickets_by_category": [
              {"category": "Bug", "count": 58 },
              {"category": "Feature Request", "count": 46 }
              ],
              "daily_trend": [
              {"date": "2026-06-01", "count": 6 },
              {"date": "2026-06-02", "count": 9 }
              ]
  }
}
GET

/members

List all active workspace members.

Returns a list of all active members in your workspace, ordered by join date. Only members with active status are included.

Request

curl https://api.usekora.app/public/v1/members \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456"

Response

{
  "success": true,
  "workspace_id": "ws_abc123",
  "members": [
    {
      "id": 1,
      "email": "john@acmesolutions.com",
      "display_name": "John",
      "role": "owner",
      "status": "active",
      "joined_at": "2026-05-01T10:00:00Z",
      "last_active_at": "2026-07-13T18:30:00Z"
    },
    {
      "id": 2,
      "email": "sarah@acmesolutions.com",
      "display_name": "Sarah",
      "role": "admin",
      "status": "active",
      "joined_at": "2026-05-02T09:00:00Z",
      "last_active_at": "2026-07-13T17:15:00Z"
    }
  ],
  "total_count": 2
}
POST

/members

Invite a new member to the workspace by email.

Creates a workspace membership with status invited. The invited user will be linked when they sign up. Enforces the workspace member limit.

Request Body

FieldTypeDescription
emailstringEmail address to invite (required)
rolestringRole: manager, operations, agent, viewer, admin (required)
curl -s -X POST "https://api.usekora.app/public/v1/members" \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456" \
  -H "Content-Type: application/json" \
  -d '{"email": "newagent@acme.com", "role": "agent"}'

Response 201 Created

{
  "success": true,
  "invitation": {
    "email": "newagent@acme.com",
    "role": "agent",
    "status": "invited",
    "invited_at": "2026-08-22T10:30:00.000Z"
  }
}
PATCH

/members/:member_id

Update a workspace member's role.

Changes the role of an active workspace member. The member_id is the user's ID returned by the List Members endpoint. Owner roles cannot be changed or assigned via this API.

Request Body

FieldTypeDescription
rolestringNew role: admin, manager, operations, agent, viewer (required)
curl -s -X PATCH "https://api.usekora.app/public/v1/members/480" \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456" \
  -H "Content-Type: application/json" \
  -d '{"role": "manager"}'

Response 200 OK

{
  "success": true,
  "member": {
    "id": 480,
    "email": "agent@acme.com",
    "name": "Jane Smith",
    "old_role": "agent",
    "new_role": "manager",
    "updated_at": "2026-08-22T10:30:00.000Z"
  }
}
POST

/webhooks

Create a new webhook endpoint programmatically.

Creates a new webhook endpoint. The URL must use HTTPS. Up to 10 webhooks per workspace. If events is omitted, all events are subscribed by default.

Request Body

FieldTypeRequiredDescription
urlstringrequiredHTTPS URL where webhook deliveries will be sent.
descriptionstringoptionalOptional description for the webhook.
eventsstring[]optionalArray of event names to subscribe to. Defaults to all events.
secret_tokenstringoptionalOptional secret token for HMAC-SHA256 signature verification.

Request

curl -X POST https://api.usekora.app/public/v1/webhooks \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/kora",
    "description": "Production webhook",
    "events": ["ticket.created", "ticket.resolved", "ticket.closed"]
  }'

Response 201 Created

{
  "success": true,
  "workspace_id": "ws_abc123",
  "webhook": {
    "id": 3,
    "url": "https://your-app.com/webhooks/kora",
    "description": "Production webhook",
    "events": ["ticket.created", "ticket.resolved", "ticket.closed"],
    "status": "active",
    "has_secret": false
  }
}
GET

/webhooks

List all webhooks for the authenticated workspace.

Returns all webhooks configured for the workspace, sorted by creation date (newest first). Secret tokens are never returned — only a boolean has_secret flag.

curl -s "https://api.usekora.app/public/v1/webhooks" \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456"

Response 200 OK

{
  "success": true,
  "webhooks": [
    {
      "id": 3,
      "url": "https://your-app.com/webhooks/kora",
      "description": "Production webhook",
      "events": ["ticket.created", "ticket.resolved"],
      "is_active": true,
      "has_secret": false,
      "created_at": "2026-08-01 10:00:00"
    }
  ],
  "total_count": 1,
  "max_allowed": 10
}
PATCH

/webhooks/:webhook_id

Update a webhook's URL, events, active state, or secret.

All fields are optional — only provided fields are updated. Pass events: [] to subscribe to all events.

Request Body (all optional)

FieldTypeDescription
urlstringNew webhook URL (must be HTTPS)
descriptionstringDescription (max 200 chars)
eventsstring[]Array of event names, or empty array for all
is_activebooleanEnable or disable the webhook
secret_tokenstringNew secret token for signature verification
curl -s -X PATCH "https://api.usekora.app/public/v1/webhooks/3" \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456" \
  -H "Content-Type: application/json" \
  -d '{"description": "Updated description", "is_active": false}'

Response 200 OK

{ "success": true }
DELETE

/webhooks/:webhook_id

Delete a webhook and all its delivery history.

Permanently deletes the webhook and all associated delivery records. This operation is irreversible.

curl -s -X DELETE "https://api.usekora.app/public/v1/webhooks/3" \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456"

Response 200 OK

{ "success": true, "deleted": true }
POST

/webhooks/:webhook_id/test

Send a test payload to a webhook to verify delivery.

Triggers a test delivery to the webhook URL. Useful for verifying that the endpoint is reachable and correctly receiving Kora payloads.

curl -s -X POST "https://api.usekora.app/public/v1/webhooks/3/test" \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456" \
  -H "Content-Type: application/json" \
  -d '{}'

Response 200 OK

{
  "success": true,
  "webhook_id": 3,
  "delivery": { "status": "delivered" }
}
GET

/webhooks/:webhook_id/deliveries

Get paginated delivery history for a webhook.

Returns delivery records sorted by creation date (newest first). Use page and limit query parameters for pagination.

Query Parameters

ParameterTypeDescription
pageintegerPage number (default: 1)
limitintegerItems per page (default: 20, max: 50)
curl -s "https://api.usekora.app/public/v1/webhooks/3/deliveries?page=1&limit=20" \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456"

Response 200 OK

{
  "success": true,
  "webhook_id": 3,
  "deliveries": [
    { "id": 15, "event": "ticket.created", "status": "delivered", "response_code": 200, "created_at": "2026-08-22 10:30:00" }
  ],
  "pagination": { "total": 1, "page": 1, "limit": 20, "total_pages": 1 }
}

Embedded Sessions

Embedded Sessions allow you to embed the Kora tickets board directly into your product via iframe — no separate login required for your users. Call the API to generate a one-time session URL, embed it, and Kora handles the rest. The embedded view renders the full tickets board with role-based access, search, filters, and ticket management.

Availability

All plans

Auth

Workspace ID + Secret Key

Token type

Time-limited

How it works: 1) Call POST /public/v1/sessions with your workspace credentials. 2) API returns a one-time session URL. 3) Embed the URL in an iframe. 4) Kora auto-authenticates the user and renders the tickets board with role-based permissions.

POST

/public/v1/sessions

Create an embedded session for external application embedding.

Creates a session URL that auto-authenticates the specified user and renders Kora inside an iframe. The session token is valid for the configured duration and can be refreshed until expiry.

Request Body

FieldTypeRequiredDescription
user_emailstringrequiredEmail of the user this session is for. Must be an active workspace member.
duration_minutesintegerrequiredSession duration in minutes. Min: 5, Max: 1440 (24 hours). Default: 480.
themestringoptionalTheme for embedded session. Default: light. Options: light, dark.

Request

curl -X POST https://api.usekora.app/public/v1/sessions \
  -H "X-Workspace-ID: ws_abc123" \
  -H "X-Secret-Key: sk_xyz456" \
  -H "Content-Type: application/json" \
  -d '{
    "user_email": "john@example.com",
    "duration_minutes": 480,
    "theme": "light"
  }'

Response 201 Created

{
  "success": true,
  "session": {
    "session_id": "sess_abc123xyz",
    "session_url": "https://usekora.app/embedd/sess_abc123xyz",
    "embed_code": "<iframe src='https://usekora.app/embedd/sess_abc123xyz' width='100%' height='100%' frameborder='0' style='border:none;'></iframe>",
    "expires_at": "2026-06-16T23:39:00Z",
    "duration_minutes": 480
  }
}

Embedding Examples

HTML

<iframe
  src="https://usekora.app/embedd/sess_abc123xyz"
  width="100%"
  height="100vh"
  frameborder="0"
  style="border: none;">
</iframe>

React

function KoraEmbed({ sessionUrl }) {
  return (
    <iframe
      src={sessionUrl}
      width="100%"
      height="100vh"
      frameBorder="0"
      style={{ border: 'none' }}
    />
  );
}

Vue

<template>
  <iframe
    :src="sessionUrl"
    width="100%"
    height="100vh"
    frameborder="0"
    style="border: none;"
  />
</template>

<script setup>
defineProps({ sessionUrl: String });
</script>

Python (Django/Flask)

<iframe
  src="{{ kora_session_url }}"
  width="100%"
  height="800px"
  frameborder="0">
</iframe>

Next.js (Server-side session creation)

// app/api/kora-session/route.ts
import { NextResponse } from 'next/server';

export async function POST(req: Request) {
  const { user_email } = await req.json();

  const res = await fetch('https://api.usekora.app/public/v1/sessions', {
    method: 'POST',
    headers: {
      'X-Workspace-ID': process.env.KORA_WORKSPACE_ID!,
      'X-Secret-Key': process.env.KORA_SECRET_KEY!,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      user_email,
      duration_minutes: 480,
      theme: 'light',
    }),
  });

  const data = await res.json();
  return NextResponse.json(data);
}

Session Behavior

PropertyDescription
Time-limitedSession stays valid for the configured duration. Refreshing the iframe re-validates successfully until expiry. First access is logged for audit (IP, user agent).
Auto-authenticationThe specified user is automatically logged in — no separate login required.
Auto-expirySession expires after duration_minutes. On expiry, shows "Session Expired" page.
Tickets onlyEmbedded sessions render the Kora tickets board exclusively. No navigation to other sections is available within the iframe.
ThemeRenders in light or dark mode based on the theme parameter.
Role-based accessThe tickets board respects the user role: operations roles see only their own reported tickets, while manager+ roles see all tickets. Ticket creation and claim actions are gated by role permissions.

Security

Session tokens are cryptographically random and unique.

First access is logged (IP, user agent) for audit. Token remains valid until expiry.

Sessions are bound to a specific workspace and user.

Session usage is logged with IP address and user agent for audit purposes.

Expired and used sessions are automatically cleaned up.

Always create sessions server-side. Never expose your X-Secret-Key in client-side code.

Webhooks

Webhooks let you receive real-time HTTP notifications when ticket events happen in your workspace. When an event occurs, Kora sends a POST request to each registered webhook URL with a JSON payload describing the event.

Webhooks are configured from Settings → Webhooks in the Kora dashboard. You can register up to 10 webhook URLs per workspace and select which events each URL should receive.

How it works

  1. A ticket event occurs (e.g. ticket created, resolved, closed).
  2. Kora finds all active webhooks subscribed to that event.
  3. For each matching webhook, Kora sends an HTTP POST with the event payload.
  4. The delivery is logged with status and response code for debugging.

Your endpoint must respond with a 2xx status code within 10 seconds. Failed deliveries are logged but not retried.

Webhook Event Catalog

Kora fires the following events. By default, a webhook receives all events. You can deselect specific events when creating or editing a webhook.

EventTriggered WhenKey Data Fields
ticket.createdA new ticket is createdticket_number, title, priority, status, reporter
ticket.updatedTicket fields are edited (title, priority, category, description)ticket_number, changes[], updated_by
ticket.resolvedTicket status changes to Resolvedticket_number, previous_status, resolved_by, resolved_at
ticket.closedTicket status changes to Closedticket_number, previous_status, closed_by, closed_at
ticket.reopenedTicket status changes to Reopenedticket_number, previous_status, reopened_by, reopened_at
ticket.assignedA ticket is assigned to a memberticket_number, assignee, previous_assignee, assigned_by
ticket.claimedAn agent claims an unassigned ticketticket_number, claimed_by, previous_status, claimed_at

Example: ticket.created payload

{
  "event": "ticket.created",
  "workspace_id": "ws_abc123",
  "timestamp": "2026-07-07T13:00:00Z",
  "data": {
    "ticket_number": 43,
    "title": "Checkout button unresponsive on mobile",
    "description": "Tapping the checkout button on iOS Safari does nothing.",
    "category": "Bug",
    "priority": "High",
    "status": "Open",
    "reporter": "John Doe",
    "reporter_email": "john@example.com",
    "assignee": null,
    "assignee_email": null,
    "created_at": "2026-07-07T13:00:00Z"
  }
}

Example: ticket.resolved payload

{
  "event": "ticket.resolved",
  "workspace_id": "ws_abc123",
  "timestamp": "2026-07-07T14:00:00Z",
  "data": {
    "ticket_number": 43,
    "title": "Checkout button unresponsive on mobile",
    "status": "Resolved",
    "previous_status": "In Progress",
    "resolved_by": "Jane Smith",
    "resolved_by_email": "jane@example.com",
    "assignee": "Jane Smith",
    "resolved_at": "2026-07-07T14:00:00Z"
  }
}

Payload Structure & Headers

Envelope

All webhook deliveries share the same envelope structure:

{
  "event": "ticket.created",
  "workspace_id": "ws_abc123",
  "timestamp": "2026-07-07T13:00:00Z",
  "data": { ... event-specific fields ... }
}

HTTP Headers

HeaderDescription
Content-Typeapplication/json
X-Kora-EventThe event name (e.g. ticket.created)
X-Kora-DeliveryUnique delivery ID for this webhook delivery
X-Kora-SignatureHMAC-SHA256 signature (only if a secret token is set)

Signature Verification

If you set a secret token when creating a webhook, Kora includes an X-Kora-Signature header containing an HMAC-SHA256 hash of the request body. Verify this signature in your endpoint to ensure the request came from Kora:

import crypto from 'crypto';

function verifySignature(rawBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  const provided = signatureHeader.replace('sha256=', '');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(provided),
  );
}

Managing Webhooks

Webhooks are managed from Settings → Webhooks in the Kora dashboard. Only workspace owners and admins can create, edit, and delete webhooks.

From the Dashboard

  • Navigate to Settings → Webhooks
  • Enter your HTTPS endpoint URL and optional description
  • Select which events the webhook should receive (all selected by default)
  • Click "Add Webhook" to register the endpoint
  • Use the "Test" button to send a test event and verify your endpoint
  • View the delivery log to see recent delivery attempts and their status
  • Pause or delete webhooks at any time

Webhook URLs must use HTTPS. Up to 10 webhooks per workspace.

Ticket Object

FieldTypeDescription
ticket_numberintegerAuto-incrementing ticket identifier, unique within a workspace.
titlestringTicket title.
descriptionstring | nullFull description text.
categorystringAI-assigned or user-set category (e.g. Bug, Feature Request).
prioritystringLow | Medium | High | Critical
statusstringOpen | Assigned | In Progress | Resolved | Closed | Reopened
reporterstringDisplay name or email of the person who created the ticket.
assigneestring | nullDisplay name or email of the assigned member. null if unassigned.
created_atISO 8601UTC timestamp when the ticket was created.
resolved_atISO 8601 | nullUTC timestamp when resolved. null if still open.
updated_atISO 8601UTC timestamp when the ticket was last updated.
attachmentsarrayList of file attachments. Each has id, file_name, file_type, file_size, storage_url.

Comment Object

FieldTypeDescription
idintegerUnique comment identifier.
contentstringComment body text.
parent_idinteger | nullID of the parent comment for threaded replies. null for top-level comments.
userstringDisplay name or email of the comment author.
created_atISO 8601UTC timestamp when the comment was posted.
attachmentsarrayList of file attachments. Each has id, file_name, file_type, file_size (bytes), and storage_url.

Response Headers

All successful responses include rate limit headers and CORS headers to support browser-based integrations.

HeaderDescription
Content-Typeapplication/json
Access-Control-Allow-Origin* — Public API endpoints are CORS-open.
X-RateLimit-LimitYour plan's daily request cap.
X-RateLimit-RemainingRequests you have left today.
X-RateLimit-ResetUnix timestamp when the window resets.

Need help or a custom endpoint?

Growth and Custom plan workspaces can request custom API creation. Reach out at support@usekora.app — we respond within 48 hours.