HaloPSA¶
HaloPSA is a professional services automation platform used by MSPs for ticketing, asset management, and time tracking.
The HaloPSA LimaCharlie Extension exposes outbound HaloPSA actions to D&R rules and AI agents: ticket lifecycle (create/update/search), notes and billable time entries, asset linkage from LC sensor telemetry to the MSP CMDB, and client/site lookup.
Setup¶
1. Create a HaloPSA API application¶
In HaloPSA, create an API application (under Configuration → Integrations → HaloPSA API) and configure it for the OAuth2 client_credentials flow:
- Authentication Method: Client ID and Secret (Services)
- Login Type: Log on as the Application (the API acts as itself, not as a specific agent)
- Permissions: grant
read:tickets,edit:tickets,read:customers,edit:customers,read:assets,edit:assets,read:actions,edit:actions
Copy the Client ID and Client Secret — you will need them in the next step. Refer to HaloPSA's own product documentation for the current UI path; the labels above may differ slightly across HaloPSA versions.
2. Subscribe to the extension¶
Subscribe to ext-halopsa from the LimaCharlie Marketplace (Extensions → Add-Ons).
3. Store the client secret¶
In Secrets Manager, create a new secret (for example halopsa-client-secret) and paste the HaloPSA Client Secret as its value.
4. Configure the extension¶
In Extensions → ext-halopsa → Configuration, fill in:
| Field | Value |
|---|---|
instance_url |
Your HaloPSA tenant URL, e.g. https://acme.halopsa.com |
client_id |
The Client ID from step 1 |
client_secret |
A reference to the secret created in step 3, e.g. hive://secret/halopsa-client-secret |
tenant |
(optional) Tenant identifier — only needed on shared-auth hosted deployments |
scope |
(optional) OAuth2 scopes (space-separated). Defaults to all. |
The configuration is validated at save time against instance_url, client_id, and client_secret. If the OAuth2 token cannot be obtained, requests will surface a 401 from the upstream HaloPSA API.
Actions¶
The extension exposes six actions, all accepting a JSON request body when invoked from a D&R rule via extension request.
create_ticket¶
Open a new ticket. Only summary is required.
| Field | Type | Notes |
|---|---|---|
summary |
string | Required. Ticket subject line. |
details |
string | Ticket body. |
client_id |
int | HaloPSA client (company) id. |
site_id |
int | HaloPSA site id. |
user_id |
int | End-user id (ticket requester). |
agent_id |
int | Agent id (assignee). |
team |
string | Team name. |
tickettype_id |
int | Ticket type id (Incident, Change, …). |
priority_id |
int | Priority id. |
status_id |
int | Initial status id. |
impact |
int | ITIL impact (1=high … 4=low). |
urgency |
int | ITIL urgency (1=high … 4=low). |
category_1 |
string | Top-level category. |
parent_id |
int | Parent ticket id (for sub-tickets). |
asset_ids |
list of int | Asset ids to attach. |
customfields |
list of object | Each entry {name\|id, value}. |
extra |
object | Raw HaloPSA ticket fields to merge into the request — escape hatch for fields not modeled above. |
Returns the created ticket, including its assigned id.
update_ticket¶
Update an existing ticket. Use to change status (which drives HaloPSA status → outcome → workflow transitions), reassign, set priority, or set the linked assets.
| Field | Type | Notes |
|---|---|---|
id |
int | Required. Ticket id to update. |
summary |
string | New summary. |
details |
string | New body. |
status_id |
int | New status id. |
agent_id |
int | New assignee. |
priority_id |
int | New priority. |
asset_ids |
list of int | Replaces the ticket's asset list. Use link_asset_to_ticket to merge a new asset into the existing list. |
customfields |
list of object | Custom fields to set. |
extra |
object | Raw HaloPSA ticket fields to merge. |
search_tickets¶
Search/list tickets. Useful to deduplicate before creating, or to look up existing work for an asset.
| Field | Type | Notes |
|---|---|---|
search |
string | Free-text search across ticket summary/details. |
client_id |
int | Restrict to a client. |
agent_id |
int | Restrict to an assignee. |
status_ids |
string | Comma-separated status ids. |
tickettype_id |
int | Restrict to a ticket type. |
page_size |
int | Default 50. |
page_no |
int | Default 1 (1-based). |
order |
string | Order-by field (e.g. id). |
orderdesc |
bool | Descending order. |
Returns { "record_count": N, "tickets": [...] }.
add_action¶
Append a HaloPSA Action to a ticket: a private note (agents only) or a public reply (visible to the end-user), optionally with billable time. Useful for AI agents to record triage findings and log work time.
| Field | Type | Notes |
|---|---|---|
ticket_id |
int | Required. Ticket id to append to. |
note |
string | Required. Note/reply content. |
hiddenfromuser |
bool | true (default) = private; false = public reply. |
timetaken |
int | Time taken on this action (whole hours only). For fractional hours, pass via extra.timetaken. |
actionchargehours |
int | Billable hours (whole hours only). For fractional hours, pass via extra.actionchargehours. |
outcome |
string | Outcome label (drives HaloPSA workflow transitions). Defaults to Note. |
extra |
object | Raw HaloPSA action fields to merge. |
Defaults to private (
hiddenfromuser=true) to avoid accidentally pushing security notes to end-users. Sethiddenfromuser: falseexplicitly for a public reply.
link_asset_to_ticket¶
Resolve a hostname to a HaloPSA asset under the given client/site, optionally creating the asset if missing, and attach it to the ticket. Bridges LC sensor telemetry to the MSP CMDB.
| Field | Type | Notes |
|---|---|---|
ticket_id |
int | Required. Ticket id to link the asset to. |
hostname |
string | Required. Hostname to resolve (matched via inventory_number / key_field). |
client_id |
int | Required when the asset must be created. |
site_id |
int | Used on asset create. |
asset_type_id |
int | Required when the asset must be created (HaloPSA rejects asset creates without an asset type). |
create_if_missing |
bool | If true (default), create the asset when no match is found. |
Returns { "asset_id": N, "asset_created": bool, "asset": {...}, "ticket": {...} }. The link is idempotent — re-running against an already-linked asset will not produce duplicates.
lookup_client_site¶
Resolve a HaloPSA client or site id from a name. Useful as plumbing for AI agents that need to map an LC org to a Halo client.
| Field | Type | Notes |
|---|---|---|
type |
enum | Required. client or site. |
search |
string | Name match. |
client_id |
int | Restrict sites to a client (only when type=site). |
page_size |
int | Default 50. |
page_no |
int | Default 1. |
Returns { "record_count": N, "clients": [...] } or { "record_count": N, "sites": [...] } depending on type.
Detection & Response¶
Example response action that opens a HaloPSA ticket for a detection:
- action: extension request
extension action: create_ticket
extension name: ext-halopsa
extension request:
summary: '{{ .cat }} - {{ .routing.hostname }}'
details: '{{ .event }}'
client_id: 12
site_id: 18
tickettype_id: 1
priority_id: 3
Wrap literal strings in
{{ "..." }}. Values underextension requestare evaluated as templates. A bare string without{{ }}is interpreted as a gjson path against the event and, if it doesn't resolve, the key is silently dropped from the payload.
extension request actions are fire-and-forget — the rule engine does not surface the response back into the rule's evaluation context, so the freshly-created ticket id is not available to a subsequent action in the same rule. Workflows that need to chain (open a ticket, then link an asset, then add a note) belong in a Playbook or an AI agent, which can hold the ticket id between calls.
To append triage findings on an existing ticket (for example from a Playbook or AI agent that already knows the ticket id), use add_action:
- action: extension request
extension action: add_action
extension name: ext-halopsa
extension request:
ticket_id: 2884
note: '{{ .routing.hostname }}: suspicious process tree observed. See LC for details.'
hiddenfromuser: true
timetaken: 1
outcome: '{{ "Note" }}'
Notes¶
- The OAuth2 access token returned by HaloPSA is cached per
(org, instance_url, client_id, secret)for the lifetime of a client. Rotating the secret in the Secrets Manager evicts the cached client on the next surfaced401. actionchargehourswill only result in a billable charge if the tenant has a charge rate configured for the agent posting the action; otherwise HaloPSA accepts the value but reportsCharge Rate: No Charge.- HaloPSA represents a ticket's main body as the first entry in its action timeline —
update_ticket.detailsreplaces that first entry rather than mutating a separate body field.