Try every feature free for 14 days, with no card. Start your trial

for the builders

Run your helpdesk from your own product

A REST API and webhooks on every plan. Create tickets from a contact form, sync customers from your CRM, reply from your app, or react to every change. The HDdesk console runs on the same API.

Create a ticket

curl https://app.hddesk.com/v1/tickets \
  -H "Authorization: Bearer $HDDESK_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subject": "Order 1182 arrived damaged",
    "requester": { "email": "jo@example.com" },
    "domain_id": "dom_01JB...",
    "tags": ["orders"]
  }'

# 201 Created
{ "id": "tkt_01JC...", "number": "ACME-1042", "status": "open" }

The basics

Base URL
https://app.hddesk.com/v1. JSON in and out, snake_case fields, ISO 8601 times in UTC.
Authentication
API keys from Settings, API keys in the console, sent as a Bearer token. Tools that only speak Basic auth can use the key as the username. Keys are read-write or read-only.
Acting as an agent
Changes are recorded as made by the key, or attributed to an agent with agent_id or the HDdesk-On-Behalf-Of header.
Rate limits
20 requests per second per account, bursts up to 40. RateLimit-* headers on every response, Retry-After on 429.
Idempotency
Send an Idempotency-Key header with POST requests; a retry within 24 hours returns the original response.
Pagination
Cursor-based. Pages stay stable while new items arrive.
Errors
HTTP status codes and one JSON error format with a type, code, message, parameter and request id.
Versioning
/v1 is stable: fields and endpoints are added, never removed or renamed.

Examples

Tickets can be referred to by their tkt_ id or by the number agents see.

Create a ticket from a contact form

curl https://app.hddesk.com/v1/tickets \
  -H "Authorization: Bearer $HDDESK_KEY" \
  -H "Idempotency-Key: form-7d1c2f" \
  -H "Content-Type: application/json" \
  -d '{"subject":"Order 1182 arrived damaged","description_text":"The box was crushed.","requester":{"email":"jo@example.com","name":"Jo Lee"},"domain_id":"dom_01JB...","tags":["orders"]}'

Reply and set the ticket to Pending

curl https://app.hddesk.com/v1/tickets/1042/reply \
  -H "Authorization: Bearer $HDDESK_KEY" -H "Content-Type: application/json" \
  -d '{"body_text":"Sorry about that, a replacement is on its way.","status":"pending"}'

List urgent open tickets in one brand

curl "https://app.hddesk.com/v1/tickets?status=open&priority=urgent&domain_id=dom_01JB..." -H "Authorization: Bearer $HDDESK_KEY"

What the API covers

Tickets and conversations

List with filters, create, update, delete and restore, bulk update, merge, split, watchers, activity. Reply, add notes, add customer messages, forward, upload attachments.

Customers and team

Contacts (including merge and erasure), companies, agents, groups. Search across tickets, contacts and companies.

Brands and settings

Domains and their templates, mailboxes, ticket types, statuses, custom fields, tags, canned responses, business hours, SLA policies, automations, views and reports.

Webhooks

Register an HTTPS endpoint and HDdesk POSTs every matching event to it, optionally only for some brands.

  • Ticket events: created, updated, status, priority, assignment, group and tag changes, merged, deleted, restored, spam
  • Message events: customer reply, agent reply, note, forward, bounce
  • SLA warnings and breaches; contact and company changes
  • Signed with HMAC-SHA256; secrets can be rotated with a 24-hour overlap
  • Retried with backoff for 3 days; delivery log, manual retry and test pings
  • GET /v1/events keeps 30 days of events as a polling fallback

Every request carries HDdesk-Signature: t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of "<t>.<raw body>" with your endpoint secret. Reject requests older than 5 minutes.

Verify a delivery in Node.js

import { createHmac, timingSafeEqual } from "node:crypto";

function verify(secret, header, rawBody) {
  const parts = header.split(",").map((p) => p.split("="));
  const t = Number(parts.find(([k]) => k === "t")?.[1]);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  return parts.filter(([k]) => k === "v1").some(([, v]) => {
    const got = Buffer.from(v, "hex");
    return got.length === expected.length && timingSafeEqual(got, expected);
  });
}

Verify a delivery in Python

import hmac, hashlib, time

def verify(secret: str, header: str, raw_body: bytes) -> bool:
    parts = [p.split("=", 1) for p in header.split(",")]
    t = int(next((v for k, v in parts if k == "t"), 0))
    if not t or abs(time.time() - t) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(v, expected) for k, v in parts if k == "v1")

The full reference

Every endpoint, field, event type and error code.