Skip to content

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/v1

Authentication

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.

PrefixBehaviour
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:

GET /tickets?page=1&limit=50
{
  "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.

GET/projects/:idOrSlugprojects:read

The 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).

GET/overviewprojects:read

One 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"
}
GET/usageprojects:read

Where 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.

MethodPathScopeDescription
POST/ticketstickets:writeCreate a ticket
GET/ticketstickets:readList tickets (paginated, filterable)
GET/tickets/:idtickets:readGet a ticket with its messages and tags
PATCH/tickets/:idtickets:writeUpdate status, priority, category, or assignee
POST/tickets/:id/messagestickets:writePost a staff reply (emails the customer)
POST/tickets/:id/tagstickets:writeAdd a tag
DELETE/tickets/:id/tagstickets:writeRemove a tag
GET/memberstickets:readList account members — the valid assignee targets

Customers

The directory builds itself from every interaction; externalId links a customer to your own app.

MethodPathScopeDescription
GET/customerscustomers:readList customers (paginated, searchable)
POST/customerscustomers:writeCreate a customer
GET/customers/:idcustomers:readGet a customer with ticket and feedback history
PATCH/customers/:idcustomers:writeUpdate a customer (email is immutable)
DELETE/customers/:idcustomers:writeDelete a customer (their tickets are untouched)

Emails

Transactional sending and the Handlebars template system.

MethodPathScopeDescription
POST/emails/sendemails:sendSend one email, immediately or scheduled
POST/emails/batchemails:sendSend up to 100 emails in one request (Starter and up)
POST/emails/previewemails:sendRender a template without sending
GET/email-templatesemails:sendList template keys — project and @account/ shared
GET/email-templates/:key/schemaemails:manageVariables a template expects
POST/email-templates/importemails:manageImport a .hbs template, layout, or partial
POST/account/email-templates/importemails:manageImport into the shared account scope

Articles

Knowledge base content.

MethodPathScopeDescription
GET/articles/searcharticles:readSearch published articles
POST/articlesarticles:writeCreate an article
GET/articles/:idarticles:readGet an article
PATCH/articles/:idarticles:writeUpdate or publish an article
GET/article-categoriesarticles:readList categories

Feedback

Ratings and sentiment, kept separate from tickets. Requires Starter and up.

MethodPathScopeDescription
GET/feedback/conversationsfeedback:readList feedback conversations
POST/feedback/conversationsfeedback:writeOpen a conversation
GET/feedback/conversations/:idfeedback:readGet a conversation with its messages
PATCH/feedback/conversations/:idfeedback:writeClose or reopen a conversation
POST/feedback/conversations/:id/messagesfeedback:writeReply (emails the customer a thread link)
GET/feedback/requestsfeedback:readList feedback requests
POST/feedback/requestsfeedback:writeSend a feedback request
GET/feedback/requests/:idfeedback:readGet a request and its funnel status

Logs

Ship application logs; they are fingerprinted and grouped into error issues.

MethodPathScopeDescription
POST/logslogs:writeIngest up to 100 log events
GET/logslogs:readList raw log events
GET/log-issueslogs:readList grouped error issues
PATCH/log-issues/:idlogs:writeAcknowledge, resolve, or reopen an issue
DELETE/log-issues/:idlogs:deleteDelete an issue and the events behind it
POST/logs/deletelogs:deleteDelete raw log events by id (max 200)

Webhooks

The signing secret is returned once, on create. Requires Starter and up.

MethodPathScopeDescription
GET/webhookswebhooks:manageList endpoints
POST/webhookswebhooks:manageRegister an endpoint
PATCH/webhooks/:idwebhooks:manageUpdate URL, events, active flag, or Slack formatting
DELETE/webhooks/:idwebhooks:manageDelete an endpoint
POST/webhooks/:id/testwebhooks:manageSend a synthetic ticket.created and report what the endpoint answered
GET/webhooks/:id/deliverieswebhooks:manageRecent 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.

MethodPathScopeDescription
GET/projectsprojects:readThe projects this key can reach, and the default
POST/projectsprojects:manageCreate a project (all-projects key)
GET/projects/:idOrSlugprojects:readSetup view: branding, public URLs, email status, widget snippet
GET/overviewprojects:readOne snapshot of the project: tickets, errors, email, feedback, agents
GET/usageprojects:readPlan tier and every quota meter next to its limit (-1 = unlimited)
POST/api-keysapi_keys:manageMint 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.json

Use it to generate a client in any language, import into Postman or Insomnia, or point an agent at it.