HeskAPI Documentation
REST API for HESK 3.7.12 · PHP 8.1+
Quick Start
Upload the drop-in zip
Extract the HeskAPI zip into your HESK root directory (same folder as hesk_settings.inc.php). The module adds an api/ directory and an admin/ panel page — no HESK files are modified.
Your license is already in place
Your download includes a ready-to-use heskapi_license.php, pre-filled and locked to your domain — just keep it in the HESK root alongside the other files. The API validates this license against your domain on startup, so don't edit or move it. (Bought before this was automatic? Re-download from your activation email, or paste the key we sent into a heskapi_license.php in the HESK root.)
Make your first API call
Send a request to /api/v1/?action=list_tickets with your API key. You should receive a JSON response with your HESK tickets.
Authentication
Every request to the HeskAPI requires an API key. Generate and manage keys from the HeskAPI admin panel inside HESK (Admin → HeskAPI Manager).
Pass the key via HTTP header (recommended):
X-HESK-API-Key: your_api_key_here
Or via query parameter:
GET /api/v1/?action=list_tickets&key=your_api_key_here
API keys can be rotated at any time from the admin panel without downtime.
Rate Limiting
HeskAPI enforces configurable per-key rate limits using atomic database counters.
| Header | Description |
|---|---|
X-RateLimit-Limit | Max requests per minute for this key |
X-RateLimit-Remaining | Requests remaining in current window |
Retry-After | Seconds until the rate limit resets (only on 429) |
When the limit is exceeded the API returns HTTP 429 with a JSON error body. Configure the limit per API key from the admin panel.
Endpoints
/api/v1/?action=list_tickets
Returns a paginated list of HESK tickets.
| Parameter | Type | Description |
|---|---|---|
page | int | Page number (default: 1) |
per_page | int | Results per page (default: 25, max: 100) |
status | int | Filter by status: 0=open, 1=resolved, 2=in progress |
category | int | Filter by HESK category ID |
/api/v1/?action=search_tickets
Full-text search across ticket subjects and messages.
| Parameter | Type | Description |
|---|---|---|
q | string | Search query (required) |
page | int | Page number |
per_page | int | Results per page |
/api/v1/?action=create_ticket
Creates a new HESK ticket. Supports multipart/form-data for file attachments and triggers native HESK email notifications. When email verification is enabled (see below), the ticket is held and this endpoint returns HTTP 202 instead of creating it immediately.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Customer name |
email | string | Yes | Customer email |
subject | string | Yes | Ticket subject |
message | string | Yes | Ticket message body |
category | int | No | HESK category ID (default: 1) |
priority | int | No | 0=Critical, 1=High, 2=Medium, 3=Low |
client_ip | string | No | Real end-user IP of the submitter (e.g. from a contact form behind Cloudflare). When passed, it is stored on the ticket and checked against HESK's banned-IP list instead of the calling server's IP. Falls back to the caller IP if omitted. |
attachment | file | No | File attachment (multipart only). Ignored while a submission is pending email verification. |
create_ticket does not create the ticket
right away — it stores the submission, emails the sender a single-use confirmation link, and returns:
HTTP 202 Accepted
{"success":true,"data":{"status":"pending_verification","email":"[email protected]"}}
The ticket is created only when the sender clicks the link (see verify_ticket). Links expire
after 72 hours, confirmation emails are rate-limited per address (excess returns HTTP 429), and attachments
are not accepted until the request is confirmed. Modes: Off, All tickets, or Unknown emails only
(addresses already known to the desk skip verification).
create_ticket then returns a
confirmation_id and emails a numeric code, which you submit to confirm_ticket
to create the ticket — ideal when your integration keeps the user on your own form:
HTTP 202 Accepted
{"success":true,"data":{"status":"pending_verification","method":"code","confirmation_id":"a1b2…","expires_in_hours":72}}/api/v1/?action=update_ticket
Updates fields on an existing ticket (status, priority, category, or custom fields).
| Field | Type | Required | Description |
|---|---|---|---|
trackid | string | Yes | Ticket track ID (e.g. ABC-123456) |
status | int | No | New status: 0=open, 1=resolved, 2=in progress |
priority | int | No | New priority (0–3) |
category | int | No | New category ID |
/api/v1/?action=close_ticket
Marks a ticket as resolved.
| Field | Type | Required | Description |
|---|---|---|---|
trackid | string | Yes | Ticket track ID |
/api/v1/?action=verify_ticket&token=…
The browser-facing confirmation link emailed to a submitter when email verification is enabled. No API key — the single-use token is the credential. Clicking it creates the held ticket, then shows a confirmation page (or redirects to the admin-configured success URL). Expired or already-used links show a friendly message. You never call this directly; it is the link embedded in the confirmation email.
| Field | Type | Required | Description |
|---|---|---|---|
token | string | Yes | Single-use confirmation token from the email (expires after 72h) |
/api/v1/?action=confirm_ticket
Confirms a submission created under the 6-digit code verification method and creates the held ticket. API key required. Submit the confirmation_id returned by create_ticket together with the code the sender received by email; on success it returns the created ticket exactly like create_ticket. The code is single-use and attempts are capped (the row is discarded after too many wrong tries).
| Field | Type | Required | Description |
|---|---|---|---|
confirmation_id | string | Yes | The handle returned by create_ticket in code mode |
code | string | Yes | The 6-digit code from the confirmation email |
Error Codes
| HTTP Status | Error Code | Description |
|---|---|---|
| 401 | invalid_key | Missing or invalid API key |
| 403 | ip_banned | Requesting IP is in HESK's ban list |
| 403 | email_banned | Email address is in HESK's ban list |
| 403 | api_disabled | API is disabled in the admin panel |
| 404 | ticket_not_found | The specified track ID does not exist |
| 422 | validation_error | Missing or invalid request parameter |
| 429 | rate_limit | Rate limit exceeded — see Retry-After header |
| 500 | server_error | Internal server error |
All error responses include a JSON body: {"status":"error","code":"...","message":"..."}
Code Examples
PHP — List tickets
$ch = curl_init('https://yoursite.com/hesk/api/v1/?action=list_tickets');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-HESK-API-Key: your_api_key_here'],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PHP — Create ticket
$ch = curl_init('https://yoursite.com/hesk/api/v1/?action=create_ticket');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => [
'name' => 'Jane Smith',
'email' => '[email protected]',
'subject' => 'Login issue',
'message' => 'I cannot log in since yesterday.',
'category' => 1, // required: target HESK category ID
'client_ip' => $_SERVER['HTTP_CF_CONNECTING_IP'] ?? $_SERVER['REMOTE_ADDR'], // real end-user IP
],
CURLOPT_HTTPHEADER => ['X-HESK-API-Key: your_api_key_here'],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
JavaScript (fetch) — List tickets
const res = await fetch('https://yoursite.com/hesk/api/v1/?action=list_tickets', {
headers: { 'X-HESK-API-Key': 'your_api_key_here' },
});
const data = await res.json();
Looking for Discord & Slack Webhooks?
The webhooks are separate drop-in scripts that post new ticket alerts to your Discord or Slack workspace. No API required — they connect directly to your HESK database via cron.
View Webhook Docs