API Reference
Integrate your Kora workspace data into dashboards, automations, and internal tools with the Kora Public API.
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/v1All 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.
| Header | Required | Description |
|---|---|---|
X-Workspace-ID | required | Your workspace's unique identifier. Find it in Settings → API inside the Kora app. |
X-Secret-Key | required | Your 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.
| Plan | Daily limit | Grace buffer |
|---|---|---|
| Free | 5,000 requests / day | +5% |
| Growth | 50,000 requests / day | +5% |
| Custom | Custom (contact sales) | +5% |
Every response includes these headers so you can track your usage:
| Header | Description |
|---|---|
X-RateLimit-Limit | Your plan's daily request cap |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | Unix 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.
| Status | Meaning |
|---|---|
400 | Bad request — missing or invalid parameters |
401 | Unauthorised — invalid or missing API key headers |
404 | Not found — the requested resource doesn't exist in your workspace |
429 | Rate limit exceeded — slow down and retry after the reset window |
500 | Internal server error — contact support@usekora.app |
// Error response shape
{
"success": false,
"error": "start_date and end_date are required",
"code": 400
}/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
| Field | Type | Description |
|---|---|---|
| workspace_id | string | Unique workspace identifier (e.g. ws_abc123) |
| name | string | Workspace display name |
| logo_url | string|null | Workspace logo URL |
| plan | string | Plan tier: free, growth, or custom |
| status | string | Workspace status: active, suspended, etc. |
| ticket_workflow | string | Workflow mode: claim_based, assignment_based, auto_claim |
| auto_close_mode | string | Auto-close mode: disabled, instant, delayed |
| auto_close_delay_days | integer | Days before auto-closing resolved tickets |
| ticket_history_days | integer | Ticket retention period in days |
| max_members | integer | Maximum allowed members for the plan |
| max_file_size_mb | integer | Maximum file size per upload in MB |
| file_storage_limit_mb | number | Total file storage limit in MB |
| api_rate_limit | string | API rate limit description (e.g. 5000/day) |
| ai_analyst_limit | integer | AI Analyst monthly usage limit |
| members_count | integer | Current active member count |
| tickets_count | integer | Total tickets in workspace |
| webhooks_count | integer | Active webhooks count |
| created_at | datetime | Workspace creation timestamp |
| activation_date | datetime|null | When the workspace was activated |
| trial_expiry_date | datetime|null | Trial 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
}
}/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
| Parameter | Type | Required | Description |
|---|---|---|---|
start_date | string | required | Start of date range. Format: YYYY-MM-DD |
end_date | string | required | End of date range. Format: YYYY-MM-DD. Max 92 days from start_date. |
reporter_email | string | optional | Filter by the reporter's email address. |
page | integer | optional | Page number (default: 1). |
limit | integer | optional | Items 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
}
}/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
| Field | Type | Required | Description |
|---|---|---|---|
title | string | required | Short title for the ticket. |
description | string | required | Full description of the issue. |
category | string | required | Category label, e.g. Bug, Feature Request, Incident. Free-form string. |
priority | string | required | One of: Low, Medium, High, Critical |
reporter_email | string | required | Email 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"
}
}/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
| Parameter | Type | Required | Description |
|---|---|---|---|
ticket_id | integer | required | The 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": []
}
}/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 status | Allowed transitions |
|---|---|
Open | Assigned, In Progress, Resolved, Closed |
Assigned | In Progress, Waiting for User, Waiting for Team, Resolved, Closed |
In Progress | Waiting for User, Waiting for Team, Resolved, Closed |
Waiting for User | In Progress, Resolved, Closed |
Waiting for Team | In Progress, Resolved, Closed |
Resolved | Closed, Reopened |
Closed | Reopened |
Reopened | Assigned, In Progress, Resolved, Closed |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
status | string | optional | New status. Must be a valid transition from current status. |
priority | string | optional | One of: Low, Medium, High, Critical |
title | string | optional | Updated ticket title. |
description | string | optional | Updated ticket description. |
category | string | optional | Updated category label. |
assignee_email | string | optional | Email 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"
}
}/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
| Field | Type | Description |
|---|---|---|
| claimer_email | string | Email 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"
}
}/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
| Field | Type | Description |
|---|---|---|
| target_ticket_id | integer | Ticket number to merge into (required) |
| merged_by_email | string | Email 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"
}/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
| Field | Type | Description |
|---|---|---|
| ticket_ids | integer[] | Array of ticket numbers (required, max 100) |
| status | string | New status (optional) |
| assignee_email | string | Email 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": []
}/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
| Parameter | Type | Required | Description |
|---|---|---|---|
ticket_id | integer | required | The 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
}/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
| Field | Type | Required | Description |
|---|---|---|---|
content | string | required | Comment body text. |
author_email | string | required | Email of an active workspace member who is the comment author. |
parent_id | integer | optional | ID 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"
}
}/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
}/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
| Field | Type | Description |
|---|---|---|
| file | binary | The file to upload (required) |
| uploader_email | string | Email 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"
}
}/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
| Parameter | Type | Required | Description |
|---|---|---|---|
month | string | required | Calendar 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 }
]
}
}/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
}/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
| Field | Type | Description |
|---|---|---|
| string | Email address to invite (required) | |
| role | string | Role: 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"
}
}/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
| Field | Type | Description |
|---|---|---|
| role | string | New 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"
}
}/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
| Field | Type | Required | Description |
|---|---|---|---|
url | string | required | HTTPS URL where webhook deliveries will be sent. |
description | string | optional | Optional description for the webhook. |
events | string[] | optional | Array of event names to subscribe to. Defaults to all events. |
secret_token | string | optional | Optional 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
}
}/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
}/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)
| Field | Type | Description |
|---|---|---|
| url | string | New webhook URL (must be HTTPS) |
| description | string | Description (max 200 chars) |
| events | string[] | Array of event names, or empty array for all |
| is_active | boolean | Enable or disable the webhook |
| secret_token | string | New 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 }/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 }/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" }
}/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
| Parameter | Type | Description |
|---|---|---|
| page | integer | Page number (default: 1) |
| limit | integer | Items 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.
/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
| Field | Type | Required | Description |
|---|---|---|---|
user_email | string | required | Email of the user this session is for. Must be an active workspace member. |
duration_minutes | integer | required | Session duration in minutes. Min: 5, Max: 1440 (24 hours). Default: 480. |
theme | string | optional | Theme 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
| Property | Description |
|---|---|
Time-limited | Session stays valid for the configured duration. Refreshing the iframe re-validates successfully until expiry. First access is logged for audit (IP, user agent). |
Auto-authentication | The specified user is automatically logged in — no separate login required. |
Auto-expiry | Session expires after duration_minutes. On expiry, shows "Session Expired" page. |
Tickets only | Embedded sessions render the Kora tickets board exclusively. No navigation to other sections is available within the iframe. |
Theme | Renders in light or dark mode based on the theme parameter. |
Role-based access | The 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
- A ticket event occurs (e.g. ticket created, resolved, closed).
- Kora finds all active webhooks subscribed to that event.
- For each matching webhook, Kora sends an HTTP POST with the event payload.
- 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.
| Event | Triggered When | Key Data Fields |
|---|---|---|
ticket.created | A new ticket is created | ticket_number, title, priority, status, reporter |
ticket.updated | Ticket fields are edited (title, priority, category, description) | ticket_number, changes[], updated_by |
ticket.resolved | Ticket status changes to Resolved | ticket_number, previous_status, resolved_by, resolved_at |
ticket.closed | Ticket status changes to Closed | ticket_number, previous_status, closed_by, closed_at |
ticket.reopened | Ticket status changes to Reopened | ticket_number, previous_status, reopened_by, reopened_at |
ticket.assigned | A ticket is assigned to a member | ticket_number, assignee, previous_assignee, assigned_by |
ticket.claimed | An agent claims an unassigned ticket | ticket_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
| Header | Description |
|---|---|
Content-Type | application/json |
X-Kora-Event | The event name (e.g. ticket.created) |
X-Kora-Delivery | Unique delivery ID for this webhook delivery |
X-Kora-Signature | HMAC-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
| Field | Type | Description |
|---|---|---|
ticket_number | integer | Auto-incrementing ticket identifier, unique within a workspace. |
title | string | Ticket title. |
description | string | null | Full description text. |
category | string | AI-assigned or user-set category (e.g. Bug, Feature Request). |
priority | string | Low | Medium | High | Critical |
status | string | Open | Assigned | In Progress | Resolved | Closed | Reopened |
reporter | string | Display name or email of the person who created the ticket. |
assignee | string | null | Display name or email of the assigned member. null if unassigned. |
created_at | ISO 8601 | UTC timestamp when the ticket was created. |
resolved_at | ISO 8601 | null | UTC timestamp when resolved. null if still open. |
updated_at | ISO 8601 | UTC timestamp when the ticket was last updated. |
attachments | array | List of file attachments. Each has id, file_name, file_type, file_size, storage_url. |
Comment Object
| Field | Type | Description |
|---|---|---|
id | integer | Unique comment identifier. |
content | string | Comment body text. |
parent_id | integer | null | ID of the parent comment for threaded replies. null for top-level comments. |
user | string | Display name or email of the comment author. |
created_at | ISO 8601 | UTC timestamp when the comment was posted. |
attachments | array | List 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.
| Header | Description |
|---|---|
Content-Type | application/json |
Access-Control-Allow-Origin | * — Public API endpoints are CORS-open. |
X-RateLimit-Limit | Your plan's daily request cap. |
X-RateLimit-Remaining | Requests you have left today. |
X-RateLimit-Reset | Unix 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.
/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
sincetimestamp. Use this for incremental polling — call every 5\u201310 seconds to get new comments without refetching the entire thread.Query Parameters
Response 200 OK