Skip to content

How Do I Integrate With the Freshservice API? (2026 Engineering Guide)

A complete engineering guide to integrating the Freshservice API in 2026 — covering Basic Auth, minute-level rate limits, page-based pagination quirks, and webhook setup.

Uday Gajavalli Uday Gajavalli · · 15 min read
How Do I Integrate With the Freshservice API? (2026 Engineering Guide)

If you're scoping a Freshservice integration for your B2B SaaS product, here's the short version: the API is RESTful, uses Basic Auth with API keys, enforces minute-level rate limits that vary by pricing plan, paginates with page and per_page query parameters (max 100 per page), and handles webhooks through a UI-based Workflow Automator rather than a programmatic subscription API. The rest of this guide goes deep on each of those areas, the edge cases your team will hit, and where the real engineering cost lives.

Why Your Customers Are Asking for a Freshservice Integration

If you are managing a B2B SaaS product, your sales team will inevitably face a prospect who says, "We love your product, but we need it to talk to our IT helpdesk before we can sign." For a significant chunk of the market, that helpdesk is Freshservice.

Freshservice holds roughly 50% market share in the help desk and ticketing category tracked by 6sense, competing with over 30 other tools, with more than 12,700 companies worldwide having adopted it. But Freshservice isn't just a support ticketing tool — it's the operational backbone for IT teams managing incidents, assets, changes, and service catalogs across IT Service Management (ITSM), IT Operations Management (ITOM), and IT Infrastructure Management (ITIM).

The platform generates over $400 million in ARR and continues to see adoption beyond IT, including HR, legal, and facilities management. Freshservice was named a Strong Performer in The Forrester Wave for Enterprise Service Management Platforms, Q4 2025, receiving the highest possible scores in Asset and Configuration Management, Process Workflow and Task Management, and Self-Service Portal criteria.

If you sell to mid-market or enterprise IT teams — whether your product is a monitoring tool, an asset management platform, an AI agent that auto-responds to tickets, or a GRC dashboard — Freshservice will keep showing up in your pipeline. Here's what building that integration actually looks like.

Understanding the Freshservice API Architecture

The current production API is v2. Freshservice APIs are plain JSON over HTTP and follow standard REST conventions supporting CRUD operations. The v1 API was deprecated in May 2023 — Freshworks advised all users to migrate to v2 and stop using v1 endpoints to avoid disruptions. If you're building a new integration, v2 is the only viable path. If you've integrated with Freshdesk before, the architecture will feel familiar — both share the same underlying design philosophy from Freshworks.

V2 brings higher rate limits from significant performance improvements, better error handling with appropriate HTTP status codes and error bodies, and minute-level rate limiting for accounts created after September 2020.

The core API surface is organized around ITSM domain objects:

Endpoint Purpose
/api/v2/tickets Incidents, service requests
/api/v2/problems Problem management
/api/v2/changes Change management
/api/v2/releases Release management
/api/v2/assets CMDB and asset lifecycle
/api/v2/requesters End users who submit tickets
/api/v2/agents IT staff who resolve tickets
/api/v2/departments Organizational structure
/api/v2/groups Agent groups for ticket routing

Freshservice also comes in three flavors, each with slightly different API behavior. The core API works the same across all three, but some endpoints and terminology differ. For example, MSPs use "Contacts" instead of "Requesters" and "Clients" instead of "Workspaces." If you're building a multi-tenant integration, you need to account for these naming differences when customers use the MSP variant.

A couple of details worth noting for your data mapping logic: blank fields are returned as null instead of being omitted, and all timestamps use UTC format (YYYY-MM-DDTHH:MM:SSZ). The null-inclusion behavior is a nice touch compared to APIs that silently drop empty fields, but it means your parsing code needs to handle nulls explicitly rather than checking for key existence.

Authentication: API Keys and Basic Auth

Freshservice does not use OAuth 2.0 for standard API access. Instead, it relies on Basic Access Authentication using a personal API key. Freshworks deprecated username/password-based authentication, replacing it with API-key-based auth for enhanced security and better API governance.

Every agent in Freshservice has an API key associated with their profile. To authenticate, pass the API key as the username in a Basic Auth header, with any string (conventionally "X") as the password:

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

Or programmatically:

const apiKey = process.env.FRESHSERVICE_API_KEY;
const encodedKey = Buffer.from(`${apiKey}:X`).toString('base64');
 
const response = await fetch(
  'https://yourdomain.freshservice.com/api/v2/tickets',
  {
    headers: {
      'Authorization': `Basic ${encodedKey}`,
      'Content-Type': 'application/json',
    },
  }
);

API key management pitfalls

Even though a user might be the account admin, API key access isn't controlled from their profile — it's a global security setting that must be enabled at the account level. This trips up almost every customer during onboarding. If a customer's API key section says "disabled," they need to check Admin → Security or contact Freshservice support.

In some Freshservice plans and regions, API key usage may be restricted and replaced with OAuth 2.0 or Service Accounts. In that case, the customer needs to raise a ticket with Freshservice support to enable API keys, or set up the integration using an OAuth app instead. For applications requiring user context, OAuth 2.0 is available — particularly valuable for marketplace applications that need to act on behalf of specific users without handling their credentials directly.

This is a real onboarding friction point. Your integration setup docs need to clearly warn users about this, or your support team will field the same "my API key is disabled" ticket every week.

Securing stored credentials

Because API keys are static and carry the exact permissions of the agent who generated them, storing them securely is non-negotiable. If you're building a multi-tenant SaaS application, encrypt these keys at rest using AES-GCM encryption with a centralized Key Management Service (KMS). Never store Freshservice API keys in plain text in your database.

Warning

Security note: Every connected API key inherits the permissions of the agent who generated it. If that agent gets deactivated or their role changes, your integration breaks silently. Build credential health checks into your connection management from day one.

Rate limiting is where most naive Freshservice integrations fall over in production. Freshservice enforces strict per-minute limits, and the rules differ based on the account's pricing tier.

Freshservice v2 rate limits by plan:

Plan Requests/Minute
Starter 100
Growth 200
Pro 400
Enterprise 500

Accounts created on or after September 2020 use these minute-level limits. Older accounts on v1 APIs were limited to 1,000 calls per hour. The limits are applied on an account-wide basis, irrespective of the number of agents or IP addresses making the calls.

That "account-wide" qualifier is the hidden landmine. Your integration shares rate limit capacity with every other tool hitting that customer's Freshservice instance — their monitoring scripts, their custom apps, their other third-party integrations. Rate limits apply to all API calls, even ones that return errors. Custom apps also count toward the limit.

Reading the rate limit headers

Every API response includes headers that tell you exactly where you stand:

X-RateLimit-Total: 400
X-RateLimit-Remaining: 198
X-RateLimit-Used-CurrentRequest: 1

When you exceed the limit, Freshservice returns a 429 Too Many Requests status code with a Retry-After header telling you how many seconds to wait before sending another request.

The embed tax

Here's the one most teams miss. Embedding additional resources (like ?include=requester) consumes 1 credit on a SHOW action but 3 credits on a LIST action. An innocent ?include=requester on a list call triples your rate limit consumption. Factor this into your capacity planning from the start, especially for background sync jobs fetching ticket lists with embedded relations.

Architecting a rate-limit-aware client

You can't rely on simple try/catch blocks. You need a centralized HTTP client that intercepts 429 errors, reads the Retry-After header, and retries with exponential backoff.

sequenceDiagram
    participant SaaS as Your Application
    participant Client as HTTP Client
    participant FS as Freshservice API

    SaaS->>Client: GET /api/v2/tickets
    Client->>FS: Request 1
    FS-->>Client: 200 OK (X-RateLimit-Remaining: 1)
    
    SaaS->>Client: GET /api/v2/tickets/123
    Client->>FS: Request 2
    FS-->>Client: 429 Too Many Requests<br>(Retry-After: 45)
    
    Note over Client: Client intercepts 429.<br>Pauses execution for 45s.
    
    Client->>FS: Request 2 (Retry)
    FS-->>Client: 200 OK
    Client-->>SaaS: Returns Data

Here's a minimal retry handler with exponential backoff:

async function freshserviceRequest(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const response = await fetch(url, options);
 
    if (response.status !== 429) {
      return response;
    }
 
    const retryAfter = parseInt(
      response.headers.get('Retry-After') || '60',
      10
    );
    const backoff = retryAfter * 1000 + attempt * 1000;
    console.warn(
      `Rate limited. Retry-After: ${retryAfter}s. Waiting ${backoff}ms.`
    );
    await new Promise((resolve) => setTimeout(resolve, backoff));
  }
 
  throw new Error('Freshservice rate limit exceeded after max retries');
}
Tip

Always prefer the Retry-After header over your own backoff calculation — it reflects the actual rate limit window reset on Freshservice's side. For a deeper dive on handling this across multiple providers, see our guide on best practices for handling API rate limits and retries.

If you're running background sync jobs across hundreds of tenants, implement a distributed queue that can pause processing for a specific tenant when a 429 is encountered, preventing your workers from spinning uselessly and extending the penalty period.

Handling Pagination in the Freshservice API

Freshservice uses page-number-based pagination with page and per_page query parameters. Unlike modern APIs that use cursor-based pagination (which is immune to data mutations during the fetch), Freshservice's approach requires careful handling.

The default page size is 30 records. The maximum per_page value is 100. Pages start at 1. Invalid values and values greater than 100 result in an error.

# First page, max page size
curl -u "API_KEY:X" \
  "https://yourdomain.freshservice.com/api/v2/tickets?per_page=100&page=1"
 
# Second page
curl -u "API_KEY:X" \
  "https://yourdomain.freshservice.com/api/v2/tickets?per_page=100&page=2"

The link header in the response holds the URL of the next page, if one exists. If you've reached the last page, the link header won't be set. However, Freshservice doesn't return total result counts or total page counts in the headers, which means you need additional logic to determine when you've consumed all pages. Your two options: follow the link header until it disappears, or check if the returned count is less than per_page. Neither is documented particularly well.

flowchart LR
    A["Start sync<br>page=1"] --> B{"Response has<br>link header?"}
    B -->|Yes| C["Increment<br>page number"]
    C --> D["Fetch next<br>page"]
    D --> B
    B -->|No| E["Sync complete"]

The "shifting window" problem

Page-based pagination is notoriously prone to race conditions. Imagine you're fetching tickets sorted by creation date (newest first). You fetch Page 1 (tickets 1–100). While you're processing those, 5 new tickets are created in Freshservice. When you request Page 2, the entire list has shifted down by 5. Tickets that were at the bottom of Page 1 are now at the top of Page 2. Your application processes those 5 tickets twice, resulting in duplicate data.

To mitigate this, sync data using a strict time window and sort by updated_at ascending:

  1. Query tickets updated between T1 and T2.
  2. Sort chronologically.
  3. Paginate through the static window.
  4. Store the timestamp of the last successfully processed ticket as your new high-water mark.

This adds significant complexity to your database schema — you must maintain state for every connected account, tracking exactly which endpoints have been synced up to which timestamps.

The filter endpoint pagination trap

Here's where things get ugly. The standard list endpoints (/api/v2/tickets) paginate normally through the full dataset. But the filter/search endpoint (/api/v2/tickets/filter) has a hard ceiling: it returns a maximum of 30 tickets per page and is capped at 10 pages — meaning a hard limit of 300 results per filter query. The per_page parameter isn't even supported on this endpoint.

This is the kind of thing that doesn't surface until you hit a customer with 5,000 open tickets. To work around it, you'll need to split filter queries by date ranges or other criteria to keep individual result sets under 300. It's tedious, and it adds real complexity to your sync logic. For a broader look at handling these differences across vendors, see how to normalize pagination across 50+ APIs.

Setting Up Webhooks for Real-Time Ticket Updates

Polling the Freshservice API every few minutes to check for new tickets is a massive waste of resources and will rapidly burn through your minute-level rate limits. For real-time updates, you need webhooks.

However, Freshservice does not offer a programmatic webhook subscription API. You cannot call an endpoint to register a callback URL. Instead, webhook delivery is configured through the Workflow Automator — a visual, UI-based automation builder inside the Freshservice admin panel.

The Workflow Automator process

To receive a webhook when a ticket is created, your customer (the Freshservice admin) must manually configure this in their UI:

  1. Navigate to Admin → Workflow Automator in their Freshservice instance.
  2. Create a new Ticket Automator.
  3. Select an event trigger (e.g., "Ticket is Raised," "Ticket is Updated").
  4. Add a Trigger Webhook action.
  5. Configure the callback URL, request method (POST), and manually construct the JSON body using Freshservice placeholders like {{ticket.id}}, {{ticket.subject}}, {{ticket.priority}}.

Due to functional limitations of the Workflow Automator, you need a separate automator for each event type. Want to capture ticket creation, ticket updates, and comment additions? That's three separate automators your customer has to build.

It's worth noting the distinction between webhooks and web requests in the Automator. Webhooks act as triggers that push data updates to external systems. Web requests are actions performed within the workflow that interact with external APIs to carry out specific operations. They serve different purposes, and the documentation conflates them occasionally.

The operational headache

This design means your customer's IT admin — not your engineering team — has to manually configure these webhooks. For a B2B product, this introduces several problems:

  • Onboarding friction: Every new customer connection requires manual Workflow Automator setup. You must provide detailed, step-by-step documentation on exactly how to configure the workflow, what headers to include for security validation, and what JSON structure your application expects.
  • No programmatic provisioning: You can't auto-create webhooks when a customer connects their account.
  • Payload inconsistency: Because the payload is manually constructed by the user, there is no guaranteed schema. If the user makes a typo in the JSON configuration, your application receives malformed data and fails to parse it.
  • No delivery guarantees: Freshservice doesn't expose a webhook delivery log or retry mechanism accessible via API.

To secure these webhooks, instruct the user to include a custom header (e.g., X-YourApp-Signature) with a static token that your backend validates before processing the payload, preventing malicious actors from sending fake ticket data to your public endpoints.

For teams that need cross-platform ticketing automation, this UI-only webhook setup is a significant operational burden at scale.

Architecting a Reliable Sync Engine

If you decide to build this in-house, you're not just writing a few API wrappers. You're building a distributed data pipeline. A production-grade sync engine for Freshservice requires several distinct components:

  1. Credential Manager: Securely stores and decrypts the Basic Auth API keys for every connected account.
  2. Job Scheduler: Triggers periodic syncs for non-webhook data (like fetching the list of Agents, Assets, or Departments).
  3. Rate Limit Coordinator: A distributed lock that tracks API usage per tenant and pauses workers before they trigger a 429, accounting for the plan-specific limits.
  4. Pagination Worker: A durable execution engine that can paginate through thousands of records, save its state, and resume exactly where it left off if a process crashes.
  5. Webhook Ingestion Gateway: A high-throughput, low-latency endpoint that accepts Workflow Automator payloads, validates security tokens, and drops the raw payloads into a message queue for asynchronous processing.

Building this infrastructure takes months of engineering time. Maintaining it takes even more.

The Hidden Costs of Building Freshservice In-House

Let's be honest about what you're signing up for. The initial integration might take a sprint — the API is logical, the docs are decent, and Basic Auth is simple. But the initial build is only 20% of the total cost of ownership. The remaining 80% is maintenance.

The ongoing engineering burden includes:

  • Rate limit management per customer: A Starter-plan customer gets 100 req/min. An Enterprise customer gets 500. Your sync logic needs to be plan-aware and share capacity with other tools hitting the same instance.
  • Pagination edge cases: Standard list endpoints and filter endpoints paginate differently. Filter endpoints have hard ceilings you need to work around. A massive enterprise customer with 500,000 tickets will time out your current sync job.
  • Credential lifecycle management: API keys are tied to individual agents. If that agent leaves the company, your integration goes down with no warning.
  • Webhook operations at scale: Manual Workflow Automator setup per customer is operational overhead that compounds with every new connection.
  • Schema drift: Freshservice ships API updates regularly. Non-breaking changes (like adding a new attribute) can happen at any time. Breaking changes come with 60 days notice — except in rare cases involving legal, performance, or security concerns, where they can drop without warning.
  • Multi-product support: If even a few customers use the MSP variant, you're handling different field names for the same concepts.

Every hour spent maintaining the Freshservice integration is an hour not spent building your core product. And if Freshservice is one of 15 ticketing tools your product needs to support, the calculus changes dramatically — that's 15 different authentication schemes, 15 different pagination strategies, and 15 different webhook implementations to maintain. For PMs evaluating this tradeoff, we wrote a playbook for how to pitch a third-party integration tool to engineering.

How Truto Simplifies Freshservice Integrations

Instead of building and maintaining this infrastructure yourself, you can use Truto's Unified Ticketing API to abstract away the complexity. Truto normalizes data across dozens of ITSM and ticketing platforms — Freshservice, Zendesk, Jira, ServiceNow, Linear — into a single, canonical schema. You write code once against the Truto API, and it works instantly for Freshservice and every other ticketing tool your customers use.

Here's how Truto eliminates the engineering headaches discussed above.

Authentication is handled and encrypted

When a customer connects their Freshservice account through Truto's embedded link flow, their API key is stored encrypted at rest. Truto manages the Basic Auth header construction and injects it into every API call — your application never touches raw credentials.

For customers whose Freshservice instances use OAuth, Truto supports that too. Token refresh happens proactively — the platform schedules a refresh ahead of token expiry and automatically retries on failure. If a credential becomes invalid, the connected account is flagged and a webhook fires to your application so you can prompt the user to reauthorize.

Pagination is standardized to cursor-based

Instead of managing Freshservice's page-number pagination (and its quirks with filter endpoints), you use Truto's unified cursor-based approach:

curl -H "Authorization: Bearer YOUR_TRUTO_TOKEN" \
  "https://api.truto.one/unified/ticketing/tickets?integrated_account_id=abc123&limit=100"

The response includes a next_cursor value. Pass it back on the next request. Done. No page counting, no filter-endpoint special cases, no link-header parsing. Truto translates the cursor to Freshservice's page parameter internally.

Rate limits are managed per account

Truto tracks rate limit headers from Freshservice responses and automatically queues, throttles, and retries requests when limits are approached. Your application receives data without needing to implement plan-aware throttling for each customer's Freshservice tier. You never see a rate limit error — the request simply succeeds.

Webhooks are normalized

Truto can sync Freshservice ticket data on a schedule using its data pipeline, so you don't need to depend on the Workflow Automator setup at all. When you use Truto's webhook delivery, you receive a standardized event payload — the same shape whether the source is Freshservice, Zendesk, or Jira.

The Proxy API for when you need to go deeper

The Unified Ticketing API covers common ITSM objects: tickets, users, contacts, comments, tags. But if you need something Freshservice-specific — like asset lifecycle data, change management approvals, or service catalog items — Truto's Proxy API lets you hit any Freshservice endpoint directly. Auth, rate limiting, and retry logic are still handled for you. You just skip the schema normalization.

# Access Freshservice's native assets endpoint through Truto's proxy
curl -H "Authorization: Bearer YOUR_TRUTO_TOKEN" \
  "https://api.truto.one/proxy/assets?integrated_account_id=abc123"

This is an important escape hatch. No unified API covers 100% of every provider's surface area — and any vendor that claims otherwise isn't being straight with you. The proxy approach means you're never blocked by a missing mapping.

When to Build, When to Buy

If Freshservice is your only integration and you have dedicated backend capacity, building it natively is reasonable. The API is well-structured, and a single integration is manageable.

But that's almost never the reality. The moment your product roadmap includes Jira Service Management, ServiceNow, Zendesk, or any other ITSM tool alongside Freshservice, you're looking at a multi-provider integration challenge where the per-provider quirks compound fast. That's the exact problem a unified API solves.

The honest trade-off: using a unified API adds a dependency. You're routing traffic through a third party, and you're constrained by their schema coverage. For most B2B SaaS teams, though, the engineering hours saved — and the faster time-to-market — more than justify that trade-off. Especially when the alternative is dedicating a full-time engineer to pagination edge cases and webhook plumbing across a dozen providers.

FAQ

How does Freshservice API authentication work?
Freshservice uses Basic Auth with your API key as the username and any string (typically "X") as the password. The key is Base64-encoded and sent in the Authorization header. Username/password authentication was deprecated in favor of API-key-based auth.
What are the Freshservice API rate limits by plan?
Freshservice v2 API enforces minute-level rate limits: Starter gets 100/min, Growth gets 200/min, Pro gets 400/min, and Enterprise gets 500/min. These are account-wide limits shared across all users, apps, and integrations hitting the same instance.
How do I paginate through Freshservice API results?
Use the page and per_page query parameters. Pages start at 1, the max per_page value is 100 (default is 30). Check the link response header — if it's absent, you've reached the last page. Note that filter endpoints cap results at 300 total (30 per page, max 10 pages).
Does Freshservice have a webhook subscription API?
No. Freshservice webhooks are configured through the Workflow Automator UI inside the admin panel. There's no programmatic API to create or manage webhook subscriptions, and each customer must manually set up separate automators for each event type.
Can I use OAuth 2.0 instead of API keys with Freshservice?
Yes, Freshservice supports OAuth 2.0, primarily for marketplace applications that need to act on behalf of specific users. In some plans and regions, API keys may be restricted in favor of OAuth. Check the customer's plan and Admin → Security settings.

More from our Blog