The staff API lets scripts and tools work with tickets and services, for example an AI assistant that drafts replies, or your own monitoring. A key acts as the staff member who created it: it has exactly that staff member’s permissions and departments, and replies are posted under their name.
Where to find it: My account → API keys. The role needs the permission Use the staff API.

Keys
- Create a key under My account → API keys, with a name and optionally Only from IPs. The key is shown once. Only its hash is stored.
- Revoke a key at any time. Last used and the last IP are shown.
- Every write and every reveal of a secret is written to the audit log.
- Failed attempts are throttled per IP: after 20 failures, the API answers
429for 15 minutes.
Requests
Base URL: https://billing.example.com/api/v1
curl -H "Authorization: Bearer <key>" https://billing.example.com/api/v1/me
Request bodies can be JSON or form fields. Answers are JSON. Errors come with an HTTP status 4xx and {"error": "…"}.
| Method and path | What it does |
|---|---|
GET /me | The staff member and the permissions of the key |
GET /tickets | Tickets of the key’s departments. Parameters: status (awaiting, active, all or a status name), dept, client, q (ticket number, e-mail or words in subject, messages or client), page, per (1–200, default 50). The answer has page and pages. |
GET /tickets/{id} | The ticket with its messages, notes, client, services, servers and credentials. Use ?by=tid to pass the ticket number instead of the internal ID. This works on every /tickets/{id}/… call. Root passwords are included only for keys with reveal credentials, and every read is logged. |
POST /tickets/{id}/reply | message, status (default Answered), operator (the user name to sign as; needs reply in the name of another staff member) |
POST /tickets/{id}/note | message: a staff note that only staff see |
POST /tickets/{id}/status | status: changes the status without e-mailing the client |
POST /tickets/{id}/update | status, priority, department_id, assigned_admin_id |
GET /tickets/{id}/attachments/{aid} | Downloads an attachment. |
GET /clients/{id}/services | The client’s services |
POST /services/{id}/password | password, ticket: replaces the stored root password, for example after a client sent the new one. Needs reveal credentials and reply. |
Example: reply to a ticket by its number
curl -X POST -H "Authorization: Bearer <key>" -H "Content-Type: application/json" \
-d '{"message":"Hello, the issue is fixed.","status":"Answered"}' \
"https://billing.example.com/api/v1/tickets/482913/reply?by=tid"
nginx and the Authorization header
With PHP-FPM behind nginx, pass the header to PHP: fastcgi_param HTTP_AUTHORIZATION $http_authorization;. Without it, every call answers 401 invalid API key. Instead of the header, you can send the key as X-Api-Key: <key>.
CWP sync API
The CWP Billing module on your CWP servers uses its own API under /api/v1/cwp/ (pair, ping, accounts, packages, report, event, link, import), with a server token per server. It is used only by the module. See CWP servers and the CWP Billing module.
Common problems
| Answer | Cause |
|---|---|
401 invalid API key | The key is wrong or revoked, or the Authorization header does not reach PHP (nginx, see above). |
403 IP not allowed for this key | The caller’s IP is not in Only from IPs. |
403 the role of this admin has no API permission | The role lacks Use the staff API. |
404 for a ticket | The ticket is in a department the key’s staff member cannot see, or you passed a ticket number without ?by=tid. |
403 permission … missing | The role lacks the permission named in the answer, for example tickets.reply. |
429 too many failed attempts | Wait 15 minutes, then fix the key. |