Stitchflow
HaloPSA logo

HaloPSA User Management API Guide

API workflow

How to automate user lifecycle operations through APIs with caveats that matter in production.

UpdatedMar 11, 2026

Summary and recommendation

HaloPSA exposes a REST API authenticated via OAuth 2.0 client_credentials grant. The base URL is instance-specific (https://{your-subdomain}.halopsa.com/api), and there is no shared multi-tenant endpoint. Tokens are short-lived; implement refresh logic before expiry to avoid mid-run auth failures.

Key scopes for identity lifecycle work are read:agents, edit:agents, read:customers, edit:customers, and read:teams. Scope assignment is configured per API application in Configuration > Integrations > HaloPSA API. There is no native SCIM 2.0 endpoint - all provisioning must go through /Agent and /Customer REST endpoints.

When building an identity graph against HaloPSA, note that agents and end users (contacts) are distinct object types at separate endpoints (/Agent vs. /Customer), with different permission models and license implications. Conflating the two in a sync pipeline is a common integration error that produces duplicate records or incorrect seat consumption.

API quick reference

Has user APIYes
Auth methodOAuth 2.0 (client_credentials grant)
Base URLOfficial docs
SCIM availableNo
SCIM plan requiredEnterprise

Authentication

Auth method: OAuth 2.0 (client_credentials grant)

Setup steps

  1. In HaloPSA, navigate to Configuration > Integrations > HaloPSA API.
  2. Create a new API application; select 'Client ID and Secret (Services)' as the authentication method.
  3. Assign the required permission scopes to the application.
  4. Note the Client ID and Client Secret.
  5. POST to https://{instance}.halopsa.com/auth/token with grant_type=client_credentials, client_id, client_secret, and scope.
  6. Use the returned access_token as a Bearer token in the Authorization header for all API requests.

Required scopes

Scope Description Required for
read:agents Read agent (technician) records GET /Agent
edit:agents Create and update agent records POST /Agent, DELETE /Agent
read:customers Read customer/contact records GET /Customer
edit:customers Create and update customer/contact records POST /Customer, DELETE /Customer
read:teams Read team records GET /Team
edit:teams Create and update team records POST /Team

User object / data model

Field Type Description On create On update Notes
id integer Unique agent identifier auto-assigned required in URL Read-only
name string Full display name of the agent required optional
email string Primary email address required optional Used for login and notifications
firstname string Agent first name optional optional
surname string Agent surname optional optional
username string Login username optional optional Defaults to email if not set
isactive boolean Whether the agent account is active optional optional Set to false to deactivate without deleting
team_id integer ID of the team the agent belongs to optional optional References /Team endpoint
role string Agent role/permission level optional optional Maps to roles configured in HaloPSA
phone string Agent phone number optional optional
mobile string Agent mobile number optional optional
department_id integer Department assignment optional optional
site_id integer Site/location assignment optional optional
timezone string Agent timezone string optional optional
language string Preferred language code optional optional

Core endpoints

List Agents

  • Method: GET
  • URL: /api/Agent
  • Watch out for: Returns all agents by default; use page_no and page_size for pagination. Inactive agents are included unless filtered.

Request example

GET /api/Agent?page_size=50&page_no=1
Authorization: Bearer {token}

Response example

{
  "record_count": 120,
  "agents": [
    {"id": 1, "name": "Jane Smith", "email": "jane@example.com", "isactive": true}
  ]
}

Get Agent by ID

  • Method: GET
  • URL: /api/Agent/{id}
  • Watch out for: Returns 404 if agent ID does not exist.

Request example

GET /api/Agent/42
Authorization: Bearer {token}

Response example

{
  "id": 42,
  "name": "Jane Smith",
  "email": "jane@example.com",
  "team_id": 3,
  "isactive": true
}

Create Agent

  • Method: POST
  • URL: /api/Agent
  • Watch out for: A license seat must be available; creating an agent when all seats are consumed will return an error.

Request example

POST /api/Agent
Content-Type: application/json

{"name": "John Doe", "email": "john@example.com", "team_id": 3}

Response example

{
  "id": 99,
  "name": "John Doe",
  "email": "john@example.com",
  "isactive": true
}

Update Agent

  • Method: POST
  • URL: /api/Agent
  • Watch out for: HaloPSA uses POST (not PATCH/PUT) for both create and update on Agent. Include the id field in the body to trigger an update.

Request example

POST /api/Agent
Content-Type: application/json

{"id": 99, "name": "John Doe Updated", "isactive": false}

Response example

{
  "id": 99,
  "name": "John Doe Updated",
  "isactive": false
}

Delete Agent

  • Method: DELETE
  • URL: /api/Agent/{id}
  • Watch out for: Deletion is permanent. Consider setting isactive=false instead to preserve audit history.

Request example

DELETE /api/Agent/99
Authorization: Bearer {token}

Response example

HTTP 200 OK
{}

List Customers (Contacts)

  • Method: GET
  • URL: /api/Customer
  • Watch out for: The /Customer endpoint returns both companies and contacts depending on query parameters; use the 'type' filter to distinguish.

Request example

GET /api/Customer?page_size=50&page_no=1
Authorization: Bearer {token}

Response example

{
  "record_count": 300,
  "customers": [
    {"id": 10, "name": "Acme Corp", "email": "contact@acme.com"}
  ]
}

Create/Update Customer

  • Method: POST
  • URL: /api/Customer
  • Watch out for: Same POST-for-upsert pattern as Agent; include id in body to update an existing record.

Request example

POST /api/Customer
Content-Type: application/json

{"name": "New Contact", "email": "new@client.com", "site_id": 5}

Response example

{
  "id": 201,
  "name": "New Contact",
  "email": "new@client.com"
}

List Teams

  • Method: GET
  • URL: /api/Team
  • Watch out for: Team IDs are required when assigning agents; retrieve this list to resolve team names to IDs.

Request example

GET /api/Team
Authorization: Bearer {token}

Response example

{
  "teams": [
    {"id": 3, "name": "Support Tier 1"}
  ]
}

Rate limits, pagination, and events

  • Rate limits: HaloPSA does not publish explicit rate limit tiers in official documentation. Limits are enforced at the instance/hosting level and may vary between cloud-hosted and self-hosted deployments.
  • Rate-limit headers: No
  • Retry-After header: No
  • Rate-limit notes: No official rate limit figures published. Self-hosted instances are subject to server resource constraints. Contact HaloPSA support for cloud throttling details.
  • Pagination method: offset
  • Default page size: 50
  • Max page size: 1000
  • Pagination pointer: page_size / page_no
Plan Limit Concurrent
Cloud (all plans) Not publicly documented 0
  • Webhooks available: No
  • Webhook notes: HaloPSA does not expose a native outbound webhook system for user/agent lifecycle events in its official documentation. Integrations are typically achieved via polling the REST API or using the built-in automation/workflow engine to trigger actions.
  • Alternative event strategy: Use HaloPSA's built-in Automation rules or scheduled API polling to detect agent/customer changes.

SCIM API status

  • SCIM available: No
  • SCIM version: Not documented
  • Plan required: Enterprise
  • Endpoint: Not documented

Limitations:

  • No native SCIM 2.0 provisioning endpoint is available in HaloPSA.
  • SAML SSO is supported (Azure AD, AuthPoint) but does not include SCIM-based user provisioning.
  • User provisioning must be handled via the REST API (/Agent, /Customer endpoints) or manual processes.

Common scenarios

Three core automation scenarios cover the agent lifecycle. First, provision on join: POST to /api/Agent with name, email, team_id, and role.

Retrieve team_id first via GET /api/Team. A failed seat check returns an error in the response body - do not rely on HTTP status alone to confirm success.

Second, deactivate on offboard: POST to /api/Agent with {"id": {agent_id}, "isactive": false}. Prefer this over DELETE /api/Agent/{id} - deletion is permanent and breaks referential integrity in historical ticket data.

Third, sync team assignments from HR: paginate GET /api/Agent (page_no, page_size up to 1000), build a team name-to-id map from GET /api/Team, then POST individual updates per agent. There is no bulk update endpoint; each change is a separate request. Pagination is offset-based using page_no (1-indexed) and page_size; use record_count from the response to calculate total pages.

Provision a new agent when an employee joins

  1. Obtain OAuth 2.0 access token via POST to /auth/token with client_credentials grant and edit:agents scope.
  2. GET /api/Team to retrieve the target team_id for the new agent.
  3. POST /api/Agent with name, email, team_id, and role fields to create the agent.
  4. Verify the returned id and isactive=true to confirm successful provisioning.
  5. If a license seat error is returned, alert the admin - no seats are available.

Watch out for: Agent creation will fail silently or return an error if named license seats are exhausted. Always check response body for error messages, not just HTTP status.

Deactivate an agent when an employee offboards

  1. Obtain access token with edit:agents scope.
  2. GET /api/Agent?search={email} to resolve the agent's id.
  3. POST /api/Agent with {"id": {agent_id}, "isactive": false} to deactivate the account.
  4. Confirm response returns isactive: false.

Watch out for: Prefer deactivation (isactive=false) over DELETE to preserve ticket history and audit trails. Deleted agents may cause referential issues in historical ticket data.

Sync agent team assignments from an external HR system

  1. Obtain access token with read:agents, edit:agents, and read:teams scopes.
  2. GET /api/Team to build a name-to-id mapping for all teams.
  3. GET /api/Agent with pagination to retrieve all current agents.
  4. For each agent whose team differs from the HR system record, POST /api/Agent with {"id": {agent_id}, "team_id": {new_team_id}}.
  5. Log all changes with the returned response for audit purposes.

Watch out for: There is no bulk update endpoint; each agent update requires a separate POST request. For large agent counts, implement rate-aware batching with delays between requests.

Why building this yourself is a trap

HaloPSA's API is functional but carries several integration traps not surfaced in standard documentation. Rate limits are not publicly documented and vary between cloud-hosted and self-hosted instances; implement exponential backoff on 429 and 503 responses as a baseline.

The POST-for-upsert pattern used on /Agent and /Customer deviates from REST conventions - omitting the id field on an intended update silently creates a duplicate record instead of returning an error.

Self-hosted instances may run different API versions with missing or modified endpoints compared to cloud; always validate against the instance's own Swagger UI at /apidoc/.

For teams maintaining an identity graph across multiple tools, HaloPSA's lack of outbound webhooks means lifecycle events must be detected via polling - there is no push notification for agent creation, deactivation, or team reassignment.

Pair the HaloPSA API with an orchestration layer such as an MCP server with 60+ deep IT/identity integrations to avoid building redundant polling logic per event type.

Automate HaloPSA workflows without one-off scripts

Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.

Every app coverage, including apps without APIs
60+ app integrations plus browser automation for apps without APIs
IT graph reconciliation across apps and your IdP
Less than a week to launch, maintained as APIs and admin consoles change
SOC 2 Type II. ~2 hours of your team's time

UpdatedMar 11, 2026

* Details sourced from official product documentation and admin references.

Keep exploring

Related apps

Abnormal Security logo

Abnormal Security

API Only
AutomationAPI only
Last updatedMar 2026

Abnormal Security is an enterprise email security platform focused on detecting and investigating threats such as phishing, account takeover (ATO), and vendor email compromise. It does not support SCIM provisioning, which means every app in your stack

ActiveCampaign logo

ActiveCampaign

API Only
AutomationAPI only
Last updatedFeb 2026

ActiveCampaign uses a group-based permission model: every user belongs to exactly one group, and all feature-area access (Contacts, Campaigns, Automations, Deals, Reports, Templates) is configured at the group level, not per individual. The default Adm

ADP logo

ADP

API Only
AutomationAPI only
Last updatedFeb 2026

ADP Workforce Now is a mid-market to enterprise HCM platform that serves as the HR source of record for employee data — payroll, benefits, time, and talent. User access is governed by a hybrid permission model: predefined security roles (Security Maste