Developer API
Public REST endpoints for creating tickets, looking up status, searching knowledge base articles, and submitting CSAT feedback.
The Workestra public API lets external systems create tickets, check status, search the knowledge base, and submit customer feedback — without needing an agent session. Use it to integrate forms, internal tools, or third-party services with your support workflow.
Base URL
https://app.workestra.comAll public endpoints live under /api/public/.
Authentication
The public API supports two auth modes:
| Mode | When to Use |
|---|---|
| API key | Backend-to-backend integrations (CRMs, sync jobs, internal tools) |
| Magic-link token | Customer-facing flows (portal links, CSAT surveys, widget submissions) |
API Key
Send the key in the Authorization header:
Authorization: Bearer wks_live_a1b2c3d4...Create keys at Support > Settings > API Keys. See API Keys for scope details.
Magic-Link Token
Token is passed as a ?t= query parameter on the URL:
GET /api/public/tickets/tkt_abc123?t=eyJhbGciOiJIUzI1...Workestra issues these tokens automatically when:
- A customer submits a ticket through the widget or portal — the confirmation email contains a tracking link
- A ticket is resolved — the CSAT survey link contains a token
- An admin shares a ticket from the agent UI
Tokens are scoped to a single ticket and expire after 30 days.
Rate Limits
Per IP address (or per API key, whichever is stricter):
| Endpoint | Limit |
|---|---|
POST /api/public/tickets | 10 per hour |
GET /api/public/tickets/[id] | 5 per minute |
POST /api/public/tickets/[id]/feedback | 3 per minute |
GET /api/public/kb/search | 5 per minute |
POST /api/public/kb/click | 30 per minute |
Hitting a limit returns 429 Too Many Requests with a Retry-After header.
Endpoints
Create a Ticket
POST /api/public/tickets
Content-Type: application/json
Authorization: Bearer wks_live_...Body:
{
"workspace_id": "wks_abc123",
"title": "Login broken on Safari",
"description": "Repro: open https://example.com in Safari 17, click Sign In...",
"type": "bug_report",
"channel": "api",
"customer_email": "alex@example.com",
"customer_name": "Alex Chen",
"priority": "high",
"cc_emails": ["manager@example.com"],
"metadata": { "source": "intercom-form" }
}Response:
{
"id": "tkt_xyz789",
"reference": "TKT-0042",
"status": "open",
"tracking_url": "https://app.workestra.com/portal/.../tickets/tkt_xyz789?t=..."
}| Field | Required | Notes |
|---|---|---|
workspace_id | Yes | Your workspace ID (find in Settings > General) |
title | Yes | Up to 200 chars |
description | No | Markdown supported |
type | No | support_ticket, customer_request, bug_report, feature_request |
channel | No | Defaults to api |
customer_email | Yes | Used for replies and tracking |
customer_name | No | Used in confirmation emails |
priority | No | low, medium, high, urgent — defaults to medium |
cc_emails | No | Up to 20 — see CC Users |
metadata | No | Free-form JSON, returned in webhook payloads |
The response includes a tracking_url with an embedded magic-link token — share this with the customer so they can view and reply to the ticket.
Get a Ticket
GET /api/public/tickets/{ticket_id}
Authorization: Bearer wks_live_...Or with a magic-link token:
GET /api/public/tickets/{ticket_id}?t={token}Response:
{
"id": "tkt_xyz789",
"reference": "TKT-0042",
"title": "Login broken on Safari",
"status": "in_progress",
"priority": "high",
"created_at": "2026-05-10T14:23:00Z",
"updated_at": "2026-05-12T09:15:00Z",
"resolved_at": null,
"comments": [
{
"id": "cmt_...",
"author_name": "Sarah (Workestra)",
"author_kind": "agent",
"body": "Thanks for reporting — investigating now.",
"created_at": "2026-05-10T14:30:00Z"
}
]
}Internal notes are never returned to magic-link calls. API-key calls with tickets.read see them.
Submit CSAT Feedback
POST /api/public/tickets/{ticket_id}/feedback?t={token}
Content-Type: application/jsonBody:
{
"score": 5,
"comment": "Solved fast and clearly. Thanks!"
}| Field | Required | Notes |
|---|---|---|
score | Yes | Integer 1–5 |
comment | No | Up to 1000 chars |
Each ticket accepts at most one feedback submission. The token is single-use and expires after the first call.
Search the Knowledge Base
GET /api/public/kb/search?workspace_id={id}&q={query}&limit={n}Returns up to limit (default 5, max 10) published KB articles matching the query. Used by the embeddable widget for ticket deflection.
Response:
{
"results": [
{
"id": "art_...",
"title": "Resetting your password",
"snippet": "...go to **Settings > Account** and click **Reset password**...",
"url": "https://app.workestra.com/portal/.../kb/art_..."
}
]
}This endpoint is unauthenticated (CORS-permissive) so the widget can call it from any origin.
Track a KB Click
POST /api/public/kb/click
Content-Type: application/json
{
"workspace_id": "wks_...",
"article_id": "art_...",
"query": "reset password"
}Fire-and-forget telemetry. Returns 200 even on internal errors (the widget shouldn't break if telemetry fails).
Errors
Errors return JSON with a stable shape:
{
"error": {
"code": "validation_error",
"message": "title is required",
"field": "title"
}
}| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error | Request body failed validation |
| 401 | unauthorized | Missing or invalid auth |
| 403 | forbidden | Auth valid but lacks the required scope |
| 404 | not_found | Ticket or workspace doesn't exist |
| 429 | rate_limited | Too many requests — see Retry-After header |
| 500 | internal_error | Unexpected server error — safe to retry |
Versioning
The public API is currently v1 (the only version). Breaking changes will be introduced at /api/public/v2/... — /api/public/... calls will keep working for at least 12 months after a v2 ships.
Webhooks vs. API
| Public API | Webhooks | |
|---|---|---|
| Direction | You call us | We call you |
| Cadence | On demand | Real-time on event |
| Best for | One-off queries, mutations | Event-driven sync |
| Use together | Yes — webhook fires, your handler queries the API for full state |