Skip to content

How to Integrate with the Freshdesk API: 2026 Engineering Guide

A hands-on engineering guide to integrating the Freshdesk API — covering authentication, pagination quirks, per-minute rate limits by plan, custom field gotchas, and where the real time sinks are.

Uday Gajavalli Uday Gajavalli · · 13 min read
How to Integrate with the Freshdesk API: 2026 Engineering Guide

If you're here, you probably have a customer (or ten) running Freshdesk, and your product team wants a native integration shipped yesterday. Freshdesk is a massive player in the customer support space. Enlyft tracks over 42,500 companies using Freshdesk, with the highest concentration in the United States and the Information Technology and Services industry. Freshdesk is most popular among companies with 10-50 employees and $1M-$10M in revenue. More recent data from 6sense puts the number even higher — over 174,000 companies globally have adopted Freshdesk for customer support. If you are building a B2B SaaS product — whether it is an AI support agent, a CRM sync tool, or an internal analytics dashboard — your sales team is going to keep hearing "we use Freshdesk" in discovery calls.

Building this integration in-house is rarely a weekend project. The API itself is logically structured, but the operational reality of syncing thousands of tickets across hundreds of connected accounts introduces real engineering overhead: strict per-minute rate limits (with per-endpoint sub-limits most guides miss), pagination ceilings that silently degrade performance, custom field naming that diverges from UI labels, and webhook delivery gaps that can cause silent data loss.

This guide breaks down exactly how to architect a native Freshdesk integration, the edge cases you will encounter, and the technical tradeoffs of maintaining it at scale.

Understanding the Freshdesk API Architecture

The current production API is v2. Freshdesk APIs are RESTful, allowing read, modify, add, and delete operations on your helpdesk data, and they support Cross-Origin Resource Sharing (CORS). The API accepts and returns JSON and supports standard HTTP methods (GET, POST, PUT, DELETE).

The base URL follows this pattern:

https://{yourdomain}.freshdesk.com/api/v2/{resource}

For most B2B SaaS integrations, you will primarily interact with these core resources:

  • Tickets: The central entity representing a customer inquiry, bug report, or feature request. Tickets contain metadata like status, priority, and requester ID.
  • Contacts: The external end-users or customers who open the tickets.
  • Companies: The organizations that Contacts belong to, which is critical for B2B account-level routing and analytics.
  • Conversations: The actual back-and-forth messages on a ticket.
  • Agents and Groups: The internal staff and team structure handling support.

Some things to know about the response format before you start writing code:

  • Blank fields are included as null instead of being omitted.
  • All timestamps are returned in UTC format: YYYY-MM-DDTHH:MM:SSZ.
  • Tickets use fixed numerical values to denote Status, Source, and Priority. For example, status 2 means "Open," priority 1 means "Low." You will need a mapping table in your code or you will be debugging mystery integers for days.

A single GET /api/v2/tickets request does not return the full conversation thread. It returns ticket metadata. To get the actual messages, you must make subsequent requests to GET /api/v2/tickets/{id}/conversations. This normalized data model is efficient for Freshdesk's internal databases, but it means your application will need multiple API calls to assemble a complete view of a single support issue. When you multiply this by thousands of tickets, you immediately run into rate limits.

Freshdesk API Authentication: HTTP Basic Auth

Unlike modern platforms that enforce OAuth 2.0 authorization code flows, Freshdesk relies on HTTP Basic Authentication. You can use your personal API key provided by Freshdesk. If you use the API Key, there is no need for any password. You can use any set of characters as a dummy password.

Here is what that looks like in practice:

curl -u YOUR_API_KEY:X \
  -H "Content-Type: application/json" \
  https://yourdomain.freshdesk.com/api/v2/tickets

The X is just a placeholder — Freshdesk ignores it entirely. Some teams use password or dummy; it does not matter.

Security considerations for multi-tenant storage

The simplicity of Basic Auth is deceptive. Because there is no token exchange or short-lived access token, you are storing a long-lived, highly privileged credential on behalf of each customer. You cannot store this API key in a plain text database column. You must implement application-level encryption at rest — a standard approach is using AES-256-GCM with a centralized Key Management Service (KMS) to handle encryption keys.

Basic Auth also means there are no granular scopes. If a user provides an admin-level API key, your application has admin-level access to their entire Freshdesk instance. You must build your own internal authorization logic to ensure your application only reads or writes the data it explicitly needs, minimizing the blast radius if a bug occurs.

The auth mechanism itself is trivial, but the credential lifecycle management — secure storage, rotation, revocation detection, and per-customer isolation — is where the engineering time actually goes. You also need to collect each customer's Freshdesk subdomain to construct the correct base URL.

Warning

Even invalid requests count towards your rate limit. If your integration has a bug that sends malformed requests, you will still burn through your quota. Validate payloads before sending.

Freshdesk paginates list endpoints using page and per_page query parameters.

By default, the number of objects returned per page is 30. This can be adjusted by adding the per_page parameter to the query string. The maximum number of objects that can be retrieved per page is 100. Invalid values and values greater than 100 will result in an error.

If a customer has 5,000 tickets, you must make at least 50 sequential API calls to retrieve them all. You cannot simply increment the page parameter until you receive an empty array. The link header in the response will hold the next page URL if it exists. When there is no link header, you have reached the end.

HTTP/1.1 200 OK
Content-Type: application/json
Link: <https://domain.freshdesk.com/api/v2/tickets?page=2&per_page=100>; rel="next"

A basic pagination loop in Python:

import requests
 
def fetch_all_tickets(domain, api_key):
    tickets = []
    page = 1
    while True:
        response = requests.get(
            f"https://{domain}.freshdesk.com/api/v2/tickets",
            auth=(api_key, "X"),
            params={"per_page": 100, "page": page}
        )
        response.raise_for_status()
        batch = response.json()
        if not batch:
            break
        tickets.extend(batch)
        if "link" not in response.headers:
            break
        page += 1
    return tickets

And a utility to parse the Link header if you need the URL directly:

function getNextPageUrl(linkHeader: string | null): string | null {
  if (!linkHeader) return null;
  const links = linkHeader.split(',');
  const nextLink = links.find(link => link.includes('rel="next"'));
  if (nextLink) {
    const match = nextLink.match(/<(.*?)>/);
    return match ? match[1] : null;
  }
  return null;
}

The deep pagination trap

Here is a gotcha that bites teams syncing large datasets: when retrieving a list of objects, avoid making calls referencing page numbers over 500. These are performance-intensive calls and you may suffer from extremely long response times.

And it gets worse for the search/filter endpoint. When using the filter tickets API, page numbers start with 1 and should not exceed 10. That is a hard ceiling of 300 results (30 per page × 10 pages) from a single filter query. If you need to export thousands of filtered tickets, you will have to break your query into smaller date ranges or use other segmentation strategies.

Freshdesk doesn't support an end date filter on the standard ticket list endpoint, only updated_since. Building efficient incremental sync logic requires careful timestamp management on your side.

Parsing Link headers manually across dozens of different integrations becomes a massive source of technical debt. If you are building multiple integrations, read our guide on how to normalize pagination and error handling across 50+ APIs.

Handling Freshdesk API Rate Limits

The number of API calls you can make is based on your plan. This limit is applied to your account irrespective of the number of agents you have or IP addresses used to make the calls. Freshdesk is currently moving all accounts from a per-hour limit to a per-minute limit.

Here are the current per-minute limits from Freshdesk's official documentation:

Plan Calls/min Ticket Create Ticket Update Tickets List Contacts List
Free 0 0 0 0 0
Growth 200 80 80 20 20
Pro 400 160 160 100 100
Enterprise 700 280 280 200 200

Notice the per-endpoint sub-limits — this is something most guides miss. Even if your Growth-plan customer has 200 calls/min total, you can only hit the Tickets List endpoint 20 times per minute. That is 20 pages × 100 records = 2,000 tickets per minute, maximum. For a customer with 50,000+ tickets, a full initial sync will take at least 25 minutes just for tickets — before you touch contacts, companies, or conversations.

For every trial period the API limit is 50 per minute. If you are developing against a trial account, expect to hit the wall fast.

Reading rate limit headers and handling 429s

You can check your current rate limit status by looking at the HTTP headers returned in response to every API request: X-Ratelimit-Total, X-Ratelimit-Remaining, and X-Ratelimit-Used-CurrentRequest.

If your API request is received after the rate limit has been reached, Freshdesk will return a 429 error. The Retry-After value in the response header will tell you how long you need to wait before you can send another request.

sequenceDiagram
    participant App as Your Application
    participant FD as Freshdesk API
    
    App->>FD: GET /api/v2/tickets (Request 201)
    FD-->>App: 429 Too Many Requests<br>Retry-After: 45
    Note over App: Pause execution for 45s
    App->>FD: GET /api/v2/tickets (Retry)
    FD-->>App: 200 OK

If you ignore the Retry-After header and continue hammering the API, Freshdesk may temporarily block the IP address or disable the API key entirely. Your HTTP client needs a circuit breaker and exponential backoff mechanism:

import time
import requests
 
def make_request_with_backoff(url, auth, params, max_retries=5):
    for attempt in range(max_retries):
        response = requests.get(url, auth=auth, params=params)
        if response.status_code == 429:
            retry_after = int(response.headers.get("Retry-After", 60))
            time.sleep(retry_after)
            continue
        response.raise_for_status()
        return response
    raise Exception("Max retries exceeded")

Shared rate limit pools

Some custom apps consume API calls and these calls also count towards the rate limit. If your customer has Freshdesk marketplace apps installed, those apps share the same pool. Your integration might get throttled because of someone else's app. Good luck debugging that one without proactively checking the X-Ratelimit-Remaining header.

If you are managing multiple accounts, you cannot use a global rate limiter. You must implement a distributed rate limiter keyed by the specific connected account ID (or the Freshdesk subdomain), ensuring that one highly active customer does not consume the connection pool and starve others. For a deeper dive, refer to our guide on best practices for handling API rate limits and retries across multiple third-party APIs.

Working with Freshdesk Custom Fields

Every B2B company customizes their Freshdesk instance. They add fields for "Subscription Tier", "Jira Issue ID", or "Account Executive". When you interact with the Tickets API, these custom fields are not returned as top-level JSON keys — they are bundled into a custom_fields object.

Names of custom fields are derived from the labels given to them during creation. If you create a custom field with the label 'test', then the name of the custom field will be 'cf_test'. All custom fields will have 'cf_' prepended to their name.

All API calls are dependent on the Name of the custom field and not its Label.

So if an agent creates a field labeled "Customer Tier" in the Freshdesk admin UI, the API key becomes cf_customer_tier. You cannot guess this reliably — spaces become underscores, special characters get stripped, and the cf_ prefix is added silently. If your application attempts to push data using the UI label instead of the exact API key, Freshdesk will reject the payload with an HTTP 400 error.

The only safe approach is to call the ticket fields endpoint first:

curl -u YOUR_API_KEY:X \
  https://yourdomain.freshdesk.com/api/v2/ticket_fields

This returns every field (default and custom) with its name, label, and type. Parse this, build your mapping, then use the name value in API calls:

{
  "subject": "Billing issue",
  "description": "Customer cannot update payment method",
  "email": "customer@example.com",
  "priority": 2,
  "status": 2,
  "custom_fields": {
    "cf_customer_tier": "Enterprise",
    "cf_account_id": "ACC-12345"
  }
}

Another gotcha: ticket webhook events do not contain custom fields. You need to make a separate View Ticket API call to get the full details including custom fields. If you are building a real-time sync driven by Freshdesk webhooks, every inbound event costs you an additional API call to hydrate the custom field data. Factor that into your rate limit budget.

This is particularly painful when you are normalizing custom field data across multiple ticketing platforms. Zendesk handles custom fields differently (they use field IDs, not names). Jira uses a completely different custom field model. If your product integrates with more than one ticketing tool, the custom field mapping logic alone becomes a maintenance burden.

Freshdesk Webhook Integration

Freshdesk supports outbound webhooks triggered by automation rules. You configure them in the Freshdesk admin panel under Admin > Automations > Rules, and they fire HTTP callbacks to your endpoint when ticket events occur.

Webhooks are subject to API rate limits based on your Freshdesk plan. If a webhook is postponed from execution for more than 24 hours due to rate limiting, Freshdesk drops the webhook and sends an alert email to the helpdesk admin.

That is a silent data loss scenario. If your customer's account is hammering the rate limit from other sources, Freshdesk will queue — and then permanently drop — your webhook deliveries. There is no retry mechanism on Freshdesk's side after that 24-hour window.

Build your webhook receiver to be idempotent, log every incoming payload, and implement a reconciliation job that periodically polls the API to catch anything the webhooks missed. This is standard practice for any third-party webhook integration, but Freshdesk's drop-after-24-hours behavior makes it non-optional.

For more on designing reliable webhook architectures across multiple ticketing platforms, see architecting cross-platform ticketing.

The Faster Way: Integrating Freshdesk with a Unified Ticketing API

Everything above is solvable. It just takes time — usually more than product teams expect. Between pagination edge cases, per-endpoint rate sub-limits, custom field discovery, and webhook reliability gaps, a "simple Freshdesk integration" easily becomes a multi-sprint project.

And here is the real problem: Freshdesk is rarely the only ticketing platform your customers use. The moment you ship Freshdesk, someone asks for Zendesk. Then Jira. Then Linear. Each has different auth (OAuth 2.0 for Zendesk, OAuth plus API tokens for Jira, API keys for Linear), different pagination (cursor-based, offset-based, page-based), different rate limit semantics, and different custom field models.

This is exactly the problem unified APIs solve. Truto's Unified Ticketing API normalizes Freshdesk, Zendesk, Jira, Linear, Front, and other ticketing platforms into a single schema with standardized resources: Tickets, Contacts, Users, Comments, and Attachments.

# List tickets from Freshdesk — same endpoint works for Zendesk, Jira, etc.
curl -H "Authorization: Bearer YOUR_TRUTO_TOKEN" \
  "https://api.truto.one/unified/ticketing/tickets?integrated_account_id=fd_acme_corp"

Here is how the platform handles the complexities outlined in this guide:

  • Zero integration-specific code. You make a single GET /unified/ticketing/tickets request. The proxy layer automatically translates this into Freshdesk's native format, calls the API, and normalizes the response back into a standard schema.
  • Automated rate limiting. The infrastructure automatically detects Freshdesk's 429 responses, parses the Retry-After header, and handles backoff and retry logic internally. Your application simply receives the successful response.
  • Transparent pagination. You do not need to parse Link headers. The pagination strategy is abstracted behind a standard cursor, regardless of whether the underlying provider uses page-based, offset-based, or cursor-based pagination.
  • Secure credential management. API keys are encrypted at rest. Your application never touches the raw credentials.
flowchart LR
    A[Your App] -->|Single API| B[Unified Ticketing API]
    B --> C[Freshdesk]
    B --> D[Zendesk]
    B --> E[Jira]
    B --> F[Linear]
    B --> G[Front]
    style A fill:#f5f5f5,stroke:#333
    style B fill:#4A90D9,stroke:#333,color:#fff

Tradeoffs to be honest about

  • You are adding a dependency. Your API calls route through additional infrastructure. Evaluate uptime guarantees, data residency policies, and security posture before committing.
  • Abstraction has limits. If you need Freshdesk-specific features that do not map to a generic ticketing model (like Scenario Automations or the Satisfaction Surveys API), you will need to use the Proxy API to make direct calls.
  • Custom fields still need mapping. A unified API normalizes the transport and shape of custom field data, but your product still needs to understand what each customer's custom fields mean. No abstraction layer can solve that semantic problem for you.

Where the approach pays off is when you are supporting three, five, or ten ticketing providers. The marginal cost of adding the next provider drops to near zero because pagination, auth, rate limiting, and schema normalization are handled once in the platform layer.

What to Build First

If you are evaluating a Freshdesk integration today, here is a practical decision framework:

  1. Freshdesk only, low volume — Build it directly. The API is straightforward, and the pagination and rate limit logic is manageable for a single provider.
  2. Freshdesk only, high volume (50K+ tickets) — Build it directly, but invest in proper incremental sync, rate limit monitoring, and a reconciliation mechanism. Budget 4-6 weeks of engineering time.
  3. Freshdesk plus 2 or more ticketing platforms — Evaluate a unified API. The engineering cost of maintaining separate auth flows, pagination strategies, rate limit handlers, and field mapping logic for each provider will exceed the cost of the abstraction layer within a few months.

The Freshdesk API itself is well-designed and stable. The pain is not in any single quirk — it is in the accumulation of quirks across every provider you need to support, multiplied by the number of customer accounts you are managing, multiplied by the years you will maintain it.

FAQ

What authentication method does the Freshdesk API use?
Freshdesk uses HTTP Basic Auth. Your API key is the username, and any arbitrary string (like 'X') is the password. No OAuth flow is required. Because the key is long-lived and has no granular scopes, you must encrypt it at rest and build your own access controls.
What are the Freshdesk API rate limits per plan?
Freshdesk enforces per-minute rate limits: Growth plan gets 200 calls/min, Pro gets 400 calls/min, and Enterprise gets 700 calls/min. Trial accounts are limited to 50/min. The limit is account-wide regardless of how many agents or IPs make the calls, and there are additional per-endpoint sub-limits.
How does Freshdesk API pagination work?
Freshdesk uses page and per_page query parameters with a maximum of 100 records per page. The response's Link header contains the next page URL. Avoid requesting pages beyond 500 due to performance degradation, and note the filter endpoint is capped at 10 pages (300 results).
How do I set custom fields when creating a Freshdesk ticket via API?
Pass custom fields inside a custom_fields object in the JSON body. The field keys use internal API names prefixed with cf_, not the UI labels. Use the GET /api/v2/ticket_fields endpoint to discover the correct API names for each customer's instance.
Can I integrate Freshdesk alongside other ticketing tools like Zendesk and Jira?
Yes. A unified ticketing API like Truto normalizes Freshdesk, Zendesk, Jira, and other platforms into a single schema, so you write one integration instead of maintaining separate codepaths for each provider's auth, pagination, and field mapping.

More from our Blog

What is a Unified API?
Engineering

What is a Unified API?

Learn how a unified API normalizes data across SaaS platforms, abstracts away authentication, and accelerates your product's integration roadmap.

Uday Gajavalli Uday Gajavalli · · 12 min read