The API sits beside HESK 3.7.x. It does not patch or replace HESK PHP files. It reads HESK settings and the HESK database, and can send notification mail through HESK’s own email functions when you ask it to.
pdo_mysql, json, mbstring, opensslcurl and zip extensions if you enable the optional updaterFallbackResource also works)*_api_* tables)unzip hesk-api-1.8.0.zip
sudo mkdir -p /var/www/hesk-api
sudo rsync -a hesk-api-1.8.0/ /var/www/hesk-api/
sudo chown -R www-data:www-data /var/www/hesk-api/storage
sudo chmod -R ug+rwX /var/www/hesk-api/storage
Adjust the web user (www-data, nginx, apache) to match the host.
cd /var/www/hesk-api
sudo -u www-data php bin/install.php \
--hesk-path=/var/www/hesk \
--api-url=https://api.help.example.com \
--create-token --user=admin --name=Bootstrap
| Flag | Meaning |
|---|---|
--hesk-path | Directory that contains hesk_settings.inc.php |
--api-url | Public URL of this API (no trailing slash) |
--hesk-url | Public HESK URL (defaults to HESK’s own hesk_url) |
--api-prefix | REST prefix, default /v1 |
--utf8mb4 | Optional: convert ticket/reply text columns to utf8mb4 (emoji) |
--create-token | Mint a staff token after schema install |
--force | Overwrite config/config.local.php |
The installer writes config/config.local.php and adds
{prefix}api_tokens and {prefix}api_audit_log.
{prefix} is whatever HESK already uses (hesk_ by default).
Save the bootstrap token when it is printed — it cannot be retrieved later.
Copy deploy/nginx-hesk-api.conf.example, set server_name and
root to …/hesk-api/public, enable the site, reload nginx, then issue TLS.
The document root must be public/. Do not expose
config/, src/, or storage/.
curl -sS https://api.help.example.com/health
You should see "status":"ok" and a version field. Then open /docs (Swagger UI) and /login (staff token portal).
Staff sign in at /login with their HESK staff username and password. Each token inherits that user’s categories, privileges, and admin flag.
php bin/create-token.php --user=admin --name="CI"
Authorization: Bearer hsk_…
X-API-Key: hsk_… is also accepted.
Shipped defaults live in config/config.php. Host overrides go in
config/config.local.php (the installer writes this file).
Environment variables HESK_PATH, HESK_URL, and
HESK_API_URL also override the matching keys.
'hesk_path' => '/var/www/hesk',
'hesk_url' => 'https://help.example.com',
'api_url' => 'https://api.help.example.com',
'api_prefix'=> '/v1',
api_url is rewritten into the live /openapi.json servers list so Swagger “Try it out” hits this install.
'branding' => [
'product_name' => 'HESK API',
'vendor' => 'Your Company',
'vendor_url' => 'https://example.com',
],
'token' => [
'prefix' => 'hsk_',
'secret_bytes' => 32,
'default_ttl_days' => null, // null = never expires
'max_per_user' => 20,
],
'rate_limit' => [
'enabled' => true,
'requests' => 120,
'window_seconds' => 60,
],
Limits are per token (or per IP before a token is presented).
'cors' => [
'allowed_origins' => ['https://app.example.com'], // or ['*']
'allowed_methods' => 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
'allowed_headers' => 'Authorization, Content-Type, X-Requested-With, X-API-Key',
],
'log' => [
'enabled' => true,
'path' => __DIR__ . '/../storage/logs/api.log',
'log_body' => false,
],
'pagination' => [
'default_limit' => 25,
'max_limit' => 100,
],
'debug' => false,
'timezone' => 'UTC',
Leave debug off in production. Never log Authorization or raw bodies.
Taken from HESK (db_pfix). You do not set it in the API config.
The sample vhost uses unix:/run/php/php8.3-fpm.sock. Change the socket
(or use 127.0.0.1:9000) to match the host. Raise
fastcgi_read_timeout if you upload large attachments.
Size, count, and allowed extensions come from HESK attachment settings.
Confirm with GET /v1/attachments/limits. PHP
upload_max_filesize and post_max_size also apply.
Time tracking is a HESK setting. When it is on, staff tokens with
can_reply_tickets or can_edit_tickets can add time:
POST /v1/tickets/{id}/replies with time_worked increments the totalPATCH /v1/tickets/{id} with time_worked sets the total; time_worked_add increments it
Accepted values: HH:MM:SS, H:MM:SS, MM:SS, minutes as a number,
or {"hours":1,"minutes":30,"seconds":0}.
00:00:00 is ignored. Do not send both fields on the same PATCH.
HESK’s staff UI prints ticket, reply, and note HTML. The API stores
message as HTML.
format is auto: real HTML tags (not <2026-08-19T21:58:12.969Z>-style timestamps) → HTML; else markdown constructs → markdown; else plain textplain: escape, autolink http(s) URLs, convert newlines to <br>html: whitelist safe tags, strip scripts/event handlers, convert leftover text newlines to <br>markdown: headings, lists, fenced code, **bold**, inline code, links, GitHub-style pipe tablesmessage_html: if set, used as HTML (same whitelist)
Pipe tables use a header row, a separator with at least three hyphens per
column (|---|---|), and body rows separated by real newlines
(HESK-stored <br> between rows is also accepted).
Alignment: :---, :---:, ---:.
Inline markdown in cells is rendered. Keep sending
"format": "markdown" — no extra flag.
curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"message":"## Done\n\n- fixed the parser\n- added tests\n\n| Check | Result |\n|---|---|\n| Helpdesk | 200 |","format":"markdown","notify":false}' \
"$API/v1/tickets/TRACK-ID/replies"
HESK 3.7 ships some text columns as utf8mb3. If ticket create fails on 4-byte UTF-8:
php bin/install.php --hesk-path=… --api-url=… --force --utf8mb4
or apply sql/utf8mb4-message-columns.sql (prefix-aware via the installer).
Document root = public/. Enable mod_rewrite and:
FallbackResource /index.php
or an equivalent rewrite of all non-files to index.php.
Full procedure (compare versions, checksum, manual replace, notify, auto-install, safety, rollback): Upgrade HESK API →
The updater ships in 1.7.0. Installs on 1.6.0 or older must use the manual steps once; after that you can enable notify or auto-install from the staff portal Updates page.
config/config.local.php (and anything you added under storage/) into the new tree.php bin/install-schema.php — it is safe to re-run (CREATE TABLE IF NOT EXISTS).Do not overwrite config.local.php with the example file.
The package can poll the TDC channel
(https://tacticaldataconcepts.com/hesk-api) for a newer version.
php bin/check-updates.php
php bin/check-updates.php --notify
php bin/check-updates.php --apply --confirm=APPLY
REST (admin / can_man_settings):
GET /v1/updates — installed vs latest (?refresh=1 to skip cache)POST /v1/updates/check — { "notify": true } opens a ticket if newerPATCH /v1/updates/settings — { "mode": "notify", "ticket": { "category": 1 } } (portal-equivalent overlay)POST /v1/updates/apply — { "confirm": "APPLY" } installs (admin only)
The staff portal Updates page (/updates) shows this
install’s version, the latest TDC version, and a link to upgrade instructions
when a newer package exists. The same knobs can be saved there (stored in
storage/updates/settings.json, kept across package replace).
'updates' => [
'enabled' => true,
'channel_url' => 'https://tacticaldataconcepts.com/hesk-api',
'mode' => 'off', // off | notify | auto
'allow_auto_install' => false, // required for unattended --cron auto-install
'ticket' => [
'category' => 1, // required for notify
'owner' => 1, // staff id or username; omit = unassigned
'priority' => 2,
],
],
Cron (example, daily 04:15). mode decides whether the run only checks, opens a ticket, or installs:
15 4 * * * www-data php /var/www/hesk-api/bin/check-updates.php --cron
Safety (all install paths):
allowed_hosts)hesk-api-{version}/ with VERSION, src/, public/index.php, config/config.php, bin/install.phpstorage/updates/backups/ (last 3 kept)config/config.local.php, hesk-api-creds.txt, or storage/allow_downgrade is true and you pass forcemode=auto and allow_auto_install=trueThe updater prefers version.json on the channel when present. If that file is missing, it uses VERSION plus hesk-api-{version}.zip and hesk-api-{version}.zip.sha256.
Remove the vhost and the API directory. Drop
{prefix}api_tokens and {prefix}api_audit_log if you want
the database clean. HESK itself is unchanged.