REST API
Standard HTTP, JSON in and out, one Bearer token. This page covers the conventions that apply everywhere; the resource pages have request and response examples for each endpoint.
Base URL
https://helmdesk.dev/api/v1Authentication
Every request carries an account API key as a Bearer token. Keys are created from Admin Settings → API keys and reach all projects or a chosen subset.
curl https://helmdesk.dev/api/v1/tickets \
-H "Authorization: Bearer sk_live_abc123..."A key that reaches one project implies it. A key that reaches several names the project per request with the Helmdesk-Project header or ?project=, or gets 400 project.required. A missing or invalid key returns 401. See Authentication for key management and IP allowlists.
Environments
The key prefix bounds the data plane; a live key may ask for sandbox per request with the Helmdesk-Environment header. Otherwise the credential is the environment, so a sandbox key cannot reach live data even by id.
| Prefix | Behaviour |
|---|---|
sk_live_ | Live data. Emails are really sent. Counts against plan quotas. |
sk_sandbox_ | Sandbox data. Emails are captured, never delivered. Quota-exempt and excluded from analytics. |
Scopes
Each key carries a scope list; the tables below name the scope every endpoint needs. A key created with an empty scope list has full access, which keeps older keys working. Calling an endpoint without its scope returns 403 auth.forbidden naming the scope you are missing.
Pagination
List endpoints take page and limit query parameters and return the rows under a named key alongside the page state:
{
"tickets": [ /* … */ ],
"total": 137,
"page": 1,
"limit": 50
}The array key matches the resource — tickets, customers, logs, issues, and so on. Article search is the one exception: it returns { results, query, count } and is not paginated.
Rate limits
100 requests per minute per API key. Over that returns 429 rate_limit.exceeded. Plan quotas (emails, log events, storage) are separate and surface as 403 with a message naming the limit.
Errors
{
"error": {
"code": "auth.forbidden",
"message": "This API key lacks the required scope: \"emails:manage\"."
}
}Every failure uses this shape. See Error Handling for the full code list.
Projects, overview and usage
Three account-level reads that let a caller — typically a coding agent holding one key — orient itself: what a project is and whether it is set up, how the app is doing right now, and where the account stands against its plan. All three require the projects:read scope.
/projects/:idOrSlugprojects:readThe setup view of one project, by slug or id: branding, the public support and knowledge-base URLs, whether email sending is configured (the from address, mode and provider name — never credentials), and the widget install snippet. The first call after creating a project. Resolved among the projects the key can reach; anything else is 403 project.forbidden.
Request
curl https://helmdesk.dev/api/v1/projects/ledgerly \
-H "Authorization: Bearer sk_live_..."
// SDK
const project = await helmdesk.projects.get('ledgerly')Response
{
"id": "5c1e7d0a-…", "slug": "ledgerly", "name": "Ledgerly",
"branding": { "brandName": "Ledgerly", "logoUrl": "…", "iconUrl": null, "primaryColor": "#6366f1", "websiteUrl": "…" },
"urls": { "support": "https://helmdesk.dev/p/ledgerly/support", "articles": "https://helmdesk.dev/p/ledgerly/articles" },
"email": { "configured": true, "fromEmail": "hello@ledgerly.app", "fromName": "Ledgerly", "sendingMode": "live", "provider": "resend", "supportEmail": "…" },
"widget": { "installSnippet": "<script src=\"https://helmdesk.dev/widget.js\" data-project=\"ledgerly\"></script>" }
}widget.identitySecret — the HMAC secret for identified widget users — is included only when the key holds projects:manage, and it is the secret for the request's environment (live or sandbox).
/overviewprojects:readOne snapshot of how the project is doing, for the request's environment: the ticket queue by status plus how many await a reply and how many of those are stale (5+ days), tickets resolved this week, open error issues and log volume in the last 24 hours, email delivery this week, feedback over 30 days, and agent actions awaiting review. Ticket definitions are the dashboard's own, so the numbers match what it shows.
Request
curl https://helmdesk.dev/api/v1/overview \
-H "Authorization: Bearer sk_live_..." \
-H "Helmdesk-Project: ledgerly"
// SDK — the client's project and environment
const overview = await helmdesk.projects.overview()Response
{
"projectId": "5c1e7d0a-…", "projectSlug": "ledgerly", "environment": "live",
"tickets": { "new": 3, "open": 7, "pending": 2, "awaitingReply": 5, "resolvedLast7d": 14, "staleAwaitingReply": 1 },
"logs": { "openErrorIssues": 2, "eventsLast24h": 418 },
"emails": { "sentLast7d": 1250, "failedLast7d": 4, "blockedLast7d": 1, "scheduled": 12 },
"feedback": { "openConversations": 3, "responsesLast30d": 21, "averageRating30d": 4.3 },
"agents": { "pendingReview": 2 },
"generatedAt": "2026-09-09T11:10:00.000Z"
}/usageprojects:readWhere the account stands against its plan — the answer to “why did I hit a cap?”. Every meter is reported next to its limit; -1 means unlimited, the same convention as the plan table. Monthly meters (tickets, emails, logs, agent runs) reset on the 1st of the UTC month given in period. Account-scoped: no project is needed.
Request
curl https://helmdesk.dev/api/v1/usage \
-H "Authorization: Bearer sk_live_..."
// SDK
const usage = await helmdesk.account.usage()Response
{
"plan": { "tier": "starter", "status": "active" },
"period": { "start": "2026-09-01T00:00:00.000Z", "end": "2026-10-01T00:00:00.000Z" },
"tickets": { "used": 412, "allowance": 1000, "overage": false },
"emails": { "used": 9800, "limit": 25000 },
"logs": { "used": 61200, "limit": 100000, "retentionDays": 7 },
"storage": { "usedBytes": 184549376, "limitBytes": 5368709120 },
"agentRuns": { "used": 120, "limit": 500 },
"projects": { "count": 2, "limit": 3 },
"teamMembers": { "count": 3, "limit": -1 }
}tickets.overage: truemeans the month's allowance is used up: paid tiers keep accepting tickets and bill the excess, Free pauses intake (the public form answers with a 503 until the month resets).
Endpoints
Tickets
The core helpdesk resource.
| Method | Path | Scope | Description |
|---|---|---|---|
| POST | /tickets | tickets:write | Create a ticket |
| GET | /tickets | tickets:read | List tickets (paginated, filterable) |
| GET | /tickets/:id | tickets:read | Get a ticket with its messages and tags |
| PATCH | /tickets/:id | tickets:write | Update status, priority, category, or assignee |
| POST | /tickets/:id/messages | tickets:write | Post a staff reply (emails the customer) |
| POST | /tickets/:id/tags | tickets:write | Add a tag |
| DELETE | /tickets/:id/tags | tickets:write | Remove a tag |
| GET | /members | tickets:read | List account members — the valid assignee targets |
Customers
The directory builds itself from every interaction; externalId links a customer to your own app.
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /customers | customers:read | List customers (paginated, searchable) |
| POST | /customers | customers:write | Create a customer |
| GET | /customers/:id | customers:read | Get a customer with ticket and feedback history |
| PATCH | /customers/:id | customers:write | Update a customer (email is immutable) |
| DELETE | /customers/:id | customers:write | Delete a customer (their tickets are untouched) |
Emails
Transactional sending and the Handlebars template system.
| Method | Path | Scope | Description |
|---|---|---|---|
| POST | /emails/send | emails:send | Send one email, immediately or scheduled |
| POST | /emails/batch | emails:send | Send up to 100 emails in one request (Starter and up) |
| POST | /emails/preview | emails:send | Render a template without sending |
| GET | /email-templates | emails:send | List template keys — project and @account/ shared |
| GET | /email-templates/:key/schema | emails:manage | Variables a template expects |
| POST | /email-templates/import | emails:manage | Import a .hbs template, layout, or partial |
| POST | /account/email-templates/import | emails:manage | Import into the shared account scope |
Articles
Knowledge base content.
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /articles/search | articles:read | Search published articles |
| POST | /articles | articles:write | Create an article |
| GET | /articles/:id | articles:read | Get an article |
| PATCH | /articles/:id | articles:write | Update or publish an article |
| GET | /article-categories | articles:read | List categories |
Feedback
Ratings and sentiment, kept separate from tickets. Requires Starter and up.
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /feedback/conversations | feedback:read | List feedback conversations |
| POST | /feedback/conversations | feedback:write | Open a conversation |
| GET | /feedback/conversations/:id | feedback:read | Get a conversation with its messages |
| PATCH | /feedback/conversations/:id | feedback:write | Close or reopen a conversation |
| POST | /feedback/conversations/:id/messages | feedback:write | Reply (emails the customer a thread link) |
| GET | /feedback/requests | feedback:read | List feedback requests |
| POST | /feedback/requests | feedback:write | Send a feedback request |
| GET | /feedback/requests/:id | feedback:read | Get a request and its funnel status |
Logs
Ship application logs; they are fingerprinted and grouped into error issues.
| Method | Path | Scope | Description |
|---|---|---|---|
| POST | /logs | logs:write | Ingest up to 100 log events |
| GET | /logs | logs:read | List raw log events |
| GET | /log-issues | logs:read | List grouped error issues |
| PATCH | /log-issues/:id | logs:write | Acknowledge, resolve, or reopen an issue |
| DELETE | /log-issues/:id | logs:delete | Delete an issue and the events behind it |
| POST | /logs/delete | logs:delete | Delete raw log events by id (max 200) |
Webhooks
The signing secret is returned once, on create. Requires Starter and up.
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /webhooks | webhooks:manage | List endpoints |
| POST | /webhooks | webhooks:manage | Register an endpoint |
| PATCH | /webhooks/:id | webhooks:manage | Update URL, events, active flag, or Slack formatting |
| DELETE | /webhooks/:id | webhooks:manage | Delete an endpoint |
| POST | /webhooks/:id/test | webhooks:manage | Send a synthetic ticket.created and report what the endpoint answered |
| GET | /webhooks/:id/deliveries | webhooks:manage | Recent deliveries, newest first (?success=, ?includePayload=) |
Projects, overview and usage
Account-level: which projects a key reaches, how an app is doing, and where the account stands against its plan.
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /projects | projects:read | The projects this key can reach, and the default |
| POST | /projects | projects:manage | Create a project (all-projects key) |
| GET | /projects/:idOrSlug | projects:read | Setup view: branding, public URLs, email status, widget snippet |
| GET | /overview | projects:read | One snapshot of the project: tickets, errors, email, feedback, agents |
| GET | /usage | projects:read | Plan tier and every quota meter next to its limit (-1 = unlimited) |
| POST | /api-keys | api_keys:manage | Mint a key for an app (projects and scopes the caller holds) |
AI endpoints are not part of the public API
The /api/v1/ai/* routes back the dashboard and the support widget. They authenticate with a signed-in session, not an API key, and are not covered by the OpenAPI spec. To drive Helmdesk from an AI assistant, use the MCP server.
OpenAPI specification
The full OpenAPI 3.1 spec — every endpoint, schema, and sample response on this site — is at:
https://helmdesk.dev/openapi.jsonUse it to generate a client in any language, import into Postman or Insomnia, or point an agent at it.