Summary and recommendation
Freshdesk's v2 REST API covers the full agent and contact lifecycle at `https://<domain>.freshdesk.com/api/v2`. Authentication is HTTP Basic Auth using an API key as the username and any non-empty string as the password - there is no OAuth 2.0 for the v2 API.
Rate limits are enforced per account (not per key), meaning all integrations sharing an account draw from the same bucket: 100 req/min on Free, up to 700 req/min on Enterprise. The API exposes `X-RateLimit-Total`, `X-RateLimit-Remaining`, and `X-RateLimit-Used-CurrentRequest` headers on every response; implement exponential backoff on HTTP 429.
For teams building identity graph pipelines, the agent object's `role_ids`, `group_ids`, and `ticket_scope` fields provide the access-layer data needed to reconstruct a user's effective permission surface across the helpdesk.
API quick reference
| Has user API | Yes |
| Auth method | HTTP Basic Auth with API key (API key as username, any string as password) |
| Base URL | Official docs |
| SCIM available | Yes |
| SCIM plan required | Enterprise |
Authentication
Auth method: HTTP Basic Auth with API key (API key as username, any string as password)
Setup steps
- Log in to your Freshdesk account.
- Navigate to Profile Settings (top-right avatar menu).
- Copy the API key displayed under 'Your API Key'.
- In API requests, use HTTP Basic Auth: set the username to your API key and the password to any non-empty string (e.g., 'X').
- Encode credentials as Base64 and pass in the Authorization header: 'Authorization: Basic <base64(api_key:X)>'.
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | integer | Unique agent/contact identifier | auto-assigned | read-only | Used in URL path for targeted operations |
| name | string | Full name of the agent or contact | required | optional | |
| string | Primary email address | required (unique) | optional | Must be unique across agents | |
| phone | string | Phone number | optional | optional | |
| mobile | string | Mobile number | optional | optional | |
| job_title | string | Agent's job title | optional | optional | Agent object only |
| role_ids | array[integer] | List of role IDs assigned to the agent | optional | optional | Agent object only; defaults to Agent role |
| group_ids | array[integer] | Groups the agent belongs to | optional | optional | Agent object only |
| ticket_scope | integer | Agent ticket permission scope: 1=Global, 2=Group, 3=Restricted | optional | optional | Agent object only |
| type | string | Agent type: 'support_agent' or 'field_agent' | optional | optional | Agent object only |
| available | boolean | Agent availability status | optional | optional | Agent object only |
| occasional | boolean | Whether agent is an occasional (collaborator) agent | optional | optional | Agent object only |
| active | boolean | Whether the agent account is active | auto | read-only | Becomes true after email confirmation |
| created_at | datetime | Timestamp of record creation (UTC) | auto-assigned | read-only | ISO 8601 format |
| updated_at | datetime | Timestamp of last update (UTC) | auto-assigned | auto-assigned | ISO 8601 format |
| avatar | object | Agent avatar image metadata | optional | optional | Multipart form upload required |
| contact.name | string | Nested contact name on agent object | derived from name | read-only via agent endpoint | Agent object embeds a contact sub-object |
| custom_fields | object | Key-value map of custom contact/agent fields | optional | optional | Field keys defined in account settings |
| tags | array[string] | Tags associated with a contact | optional | optional | Contact object only |
| company_id | integer | Primary company the contact belongs to | optional | optional | Contact object only |
Core endpoints
List all agents
- Method: GET
- URL:
https://<domain>.freshdesk.com/api/v2/agents - Watch out for: Returns only active agents by default. Use ?active=false to include deactivated agents. Max 100 per page.
Request example
GET /api/v2/agents?page=1&per_page=100
Authorization: Basic <base64(api_key:X)>
Response example
[
{
"id": 1,
"name": "Jane Doe",
"email": "jane@example.com",
"active": true,
"role_ids": [1],
"created_at": "2023-01-10T08:00:00Z"
}
]
Get a single agent
- Method: GET
- URL:
https://<domain>.freshdesk.com/api/v2/agents/{agent_id} - Watch out for: Returns 404 if agent_id does not exist or belongs to a different account.
Request example
GET /api/v2/agents/1
Authorization: Basic <base64(api_key:X)>
Response example
{
"id": 1,
"name": "Jane Doe",
"email": "jane@example.com",
"ticket_scope": 1,
"role_ids": [1],
"group_ids": [2]
}
Create an agent
- Method: POST
- URL:
https://<domain>.freshdesk.com/api/v2/agents - Watch out for: Newly created agents are inactive (active=false) until they confirm their email. Agent seat limits apply; exceeding them returns HTTP 400.
Request example
POST /api/v2/agents
Content-Type: application/json
{
"name": "John Smith",
"email": "john@example.com",
"ticket_scope": 1,
"role_ids": [1]
}
Response example
{
"id": 42,
"name": "John Smith",
"email": "john@example.com",
"active": false,
"created_at": "2024-06-01T10:00:00Z"
}
Update an agent
- Method: PUT
- URL:
https://<domain>.freshdesk.com/api/v2/agents/{agent_id} - Watch out for: Uses PUT (full-style), but only supplied fields are updated. Email cannot be changed via this endpoint after creation.
Request example
PUT /api/v2/agents/42
Content-Type: application/json
{
"job_title": "Support Lead",
"group_ids": [3, 5]
}
Response example
{
"id": 42,
"name": "John Smith",
"job_title": "Support Lead",
"group_ids": [3, 5],
"updated_at": "2024-06-02T09:00:00Z"
}
Delete (deactivate) an agent
- Method: DELETE
- URL:
https://<domain>.freshdesk.com/api/v2/agents/{agent_id} - Watch out for: DELETE deactivates the agent (sets active=false) rather than permanently deleting the record. The agent's contact record is retained.
Request example
DELETE /api/v2/agents/42
Authorization: Basic <base64(api_key:X)>
Response example
HTTP 204 No Content
List all contacts
- Method: GET
- URL:
https://<domain>.freshdesk.com/api/v2/contacts - Watch out for: Supports filter params: email, mobile, phone, company_id, updated_since. Deleted contacts require ?deleted=true.
Request example
GET /api/v2/contacts?page=1&per_page=100
Authorization: Basic <base64(api_key:X)>
Response example
[
{
"id": 101,
"name": "Alice",
"email": "alice@customer.com",
"active": true,
"company_id": 5
}
]
Create a contact
- Method: POST
- URL:
https://<domain>.freshdesk.com/api/v2/contacts - Watch out for: At least one of email, phone, mobile, or twitter_id is required. Duplicate email returns HTTP 409.
Request example
POST /api/v2/contacts
Content-Type: application/json
{
"name": "Bob Jones",
"email": "bob@customer.com",
"phone": "+14155550100"
}
Response example
{
"id": 202,
"name": "Bob Jones",
"email": "bob@customer.com",
"active": true,
"created_at": "2024-06-01T11:00:00Z"
}
Convert contact to agent
- Method: PUT
- URL:
https://<domain>.freshdesk.com/api/v2/contacts/{contact_id}/make_agent - Watch out for: Consumes an agent seat. New agent is inactive until email is confirmed. Fails if seat limit is reached.
Request example
PUT /api/v2/contacts/202/make_agent
Content-Type: application/json
{
"ticket_scope": 1,
"role_ids": [1]
}
Response example
{
"id": 43,
"email": "bob@customer.com",
"active": false,
"ticket_scope": 1
}
Rate limits, pagination, and events
- Rate limits: Rate limits are plan-based and applied per account. Limits are expressed as requests per minute (RPM). Freshdesk returns rate-limit headers on every response.
- Rate-limit headers: Yes
- Retry-After header: No
- Rate-limit notes: Headers returned: X-RateLimit-Total (limit), X-RateLimit-Remaining (remaining), X-RateLimit-Used-CurrentRequest (cost of last request). When limit is exceeded, HTTP 429 is returned. Retry after the current minute window resets.
- Pagination method: offset
- Default page size: 30
- Max page size: 100
- Pagination pointer: page (1-based) and per_page
| Plan | Limit | Concurrent |
|---|---|---|
| Free (Sprout) | 100 req/min | 0 |
| Growth (Blossom/Garden) | 200 req/min | 0 |
| Pro (Estate) | 400 req/min | 0 |
| Enterprise (Forest) | 700 req/min | 0 |
- Webhooks available: Yes
- Webhook notes: Freshdesk supports webhooks via Automation rules (Ticket automations and Observer rules). Webhooks can be triggered on ticket events but are not natively available for agent/contact lifecycle events (create, update, delete) directly.
- Alternative event strategy: For agent/contact change events, poll the List Agents or List Contacts endpoints using the updated_since filter parameter, or use Freshdesk's native SCIM provisioning for identity lifecycle events via your IdP.
- Webhook events: ticket.created, ticket.updated, ticket.resolved, ticket.closed, ticket.reopened
SCIM API status
SCIM available: Yes
SCIM version: 2.0
Plan required: Enterprise
Endpoint: https://
.freshdesk.com/scim/v2 Supported operations: GET /Users (list users), GET /Users/{id} (get user), POST /Users (provision user), PUT /Users/{id} (replace user), PATCH /Users/{id} (update user), DELETE /Users/{id} (deprovision user)
Limitations:
- Requires SSO (SAML) to be configured before enabling SCIM.
- Only available on the Enterprise plan ($79/agent/month billed annually).
- Group (team) provisioning support depends on IdP integration; not all IdPs support Freshdesk SCIM Groups.
- SCIM token is separate from the REST API key and is generated in the Security Settings page.
- Supported IdPs include Okta, Microsoft Entra ID (Azure AD), and OneLogin.
Common scenarios
Three scenarios cover the majority of programmatic user management needs. For agent onboarding: POST /api/v2/agents with name, email, ticket_scope, and role_ids; the agent is created with active=false until email confirmation, so poll GET /api/v2/agents/{id} before treating the account as usable.
Seat limits are enforced at creation time - exceeding them returns HTTP 400, so verify available capacity before bulk provisioning. For contact sync from an external CRM: paginate GET `/api/v2/contacts?
updated_since=, deduplicate by email before posting (duplicate email returns HTTP 409), and track X-RateLimit-Remaining` to avoid throttling.
For SCIM-based deprovisioning on Enterprise: generate a SCIM bearer token separately from Admin > Security (it is not the REST API key), configure your IdP to send DELETE /scim/v2/Users/{id}, and confirm deactivation via GET /api/v2/agents/{id} checking active=false.
Onboard a new support agent
- POST /api/v2/agents with name, email, ticket_scope, and role_ids to create the agent record.
- Agent receives an invitation email; account is inactive (active=false) until confirmed.
- Optionally PUT /api/v2/agents/{id} to assign group_ids and job_title after creation.
- Poll GET /api/v2/agents/{id} to check active=true once the agent confirms their email.
Watch out for: Agent seat limits are enforced at creation time. Verify available seats before bulk provisioning to avoid HTTP 400 errors.
Bulk-sync contacts from an external CRM
- GET /api/v2/contacts?updated_since=
&page=1&per_page=100 to fetch recently changed contacts. - Paginate through all pages until a page returns fewer than per_page results.
- For each contact not in Freshdesk, POST /api/v2/contacts with at minimum name and email.
- For existing contacts, PUT /api/v2/contacts/{id} with changed fields.
- Track X-RateLimit-Remaining and pause if approaching zero to avoid HTTP 429.
Watch out for: Duplicate email addresses return HTTP 409. Deduplicate by email before posting. The updated_since filter uses UTC ISO 8601 and is inclusive.
Deprovision an agent via SCIM (Enterprise)
- Ensure SSO (SAML) is configured and SCIM is enabled in Admin > Security.
- Generate a SCIM bearer token from the Security Settings page.
- In your IdP (Okta, Entra ID, OneLogin), unassign the user from the Freshdesk SCIM application.
- The IdP sends DELETE /scim/v2/Users/{scim_user_id} to Freshdesk automatically.
- Freshdesk deactivates the agent; verify by GET /api/v2/agents/{id} and confirming active=false.
Watch out for: SCIM deprovisioning requires the Enterprise plan and an active SSO configuration. Removing SCIM access does not delete the agent's historical ticket data.
Why building this yourself is a trap
Several API behaviors are non-obvious and will cause silent failures if not handled explicitly. DELETE on /api/v2/agents/{id} deactivates the agent rather than permanently removing the record - the contact record is retained and the agent continues to appear in historical data.
The List Agents endpoint returns only active agents by default; pass ?active=false to include deactivated accounts, which matters for identity graph reconciliation where stale access must be detected. Pagination is 1-based with no cursor and no total-count header - the only termination signal is a page returning fewer results than per_page.
Freshdesk does not emit webhooks for agent or contact lifecycle events; change detection requires polling with the updated_since filter. Custom agent and contact fields must be pre-created in the admin UI before they can be written via API, making field-level provisioning dependent on out-of-band configuration.
Finally, SCIM provisioning requires an active SAML SSO configuration as a prerequisite - SCIM cannot be enabled independently.
Automate Freshdesk 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.