The real problem
Why this is harder than it looks
Freshdesk doesn't use OAuth. Unlike most modern SaaS APIs, Freshdesk authenticates via API key passed as Basic Auth — each agent in your customers' helpdesks has their own API key tied to their profile and role. There is no OAuth consent flow, no access token to exchange, no refresh cycle. What sounds like a simplification is actually where the pain starts.
In production, your AI agent acts on behalf of individual support agents. That means you need each agent's API key collected, stored securely, and isolated so one agent's credential is never used in the context of another. Because these are long-lived credentials — not short-lived tokens — the security burden is higher, not lower. A leaked API key doesn't expire in an hour. The agent role attached to it also determines what the API will permit: a restricted agent cannot reply to tickets the way an admin can. Your code has to handle permission failures gracefully, not assume uniform access across all users.
Freshdesk API calls are also subdomain-scoped. Every customer's helpdesk lives at their-company.freshdesk.com. If you're building a product that connects to many customers' Freshdesk accounts, you need to store and route to each customer's subdomain alongside their key. Miss this and your calls go to the wrong account entirely — with no error that makes the root cause obvious.
None of this is insurmountable. But collecting API keys from users is friction-heavy UX that most users can't complete without hand-holding. Storing them securely across tenants is infrastructure work. Detecting when a key is revoked or reset — and surfacing that cleanly rather than failing silently — adds more. Scalekit handles all of it as the credential vault layer between your agent and Freshdesk. Your agent names a tool. Scalekit routes the call to the right subdomain with the right credential.
Capabilities
What your agent can do with Freshdesk
Once connected, your agent has 10 pre-built tools covering the core Freshdesk support workflow:
- Create, retrieve, and update tickets: with priority, status, assignment, tags, and custom fields
- Reply to ticket conversations: posting public replies on behalf of the connected support agent, with CC and BCC support
- List and filter tickets: by queue (new_and_my_open, watching, spam), requester, company, or timestamp
- Create and look up contacts: with company association, custom fields, language, and segmentation tags
- Manage agents and roles: create, delete, and list agents; retrieve available roles for assignment
Setup context
What we're building
This guide connects a support triage agent to Freshdesk — helping support agents create tickets, reply to conversations, look up contacts, and manage queues without leaving your product.
🤖
Example agent
Support triage assistant managing helpdesk tickets on behalf of each support agent
🔐
Auth model
B2B SaaS — each support agent connects their own Freshdesk account. identifier = your user ID
Setup
1 Setup: One SDK, One credential
Install the Scalekit SDK. The only credential your application manages is the Scalekit API key — no Freshdesk API keys, no subdomain strings, nothing belonging to your customers.
pip install scalekit-sdk-python
import scalekit.client
import os
from dotenv import load_dotenv
load_dotenv()
scalekit = scalekit.client.ScalekitClient(
client_id=os.getenv("SCALEKIT_CLIENT_ID"),
client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"),
env_url=os.getenv("SCALEKIT_ENV_URL"),
)
actions = scalekit.actions
Connected Accounts
2 Per-User Auth: Creating connected accounts
Each support agent gets their own Connected Account, giving them a dedicated auth context. The identifier is any unique string from your system — a UUID, email, whatever you use internally.
response = actions.get_or_create_connected_account(
connection_name="freshdesk",
identifier="user_fd_123" # your internal user ID
)
connected_account = response.connected_account
print(f"Status: {connected_account.status}")
# Status: PENDING — user hasn't authorized yet
This call is idempotent — safe to call on every session start. Returns the existing account if one already exists.
Authorization Flow
3 The authorization flow
The support agent authorizes your integration once. Scalekit presents a secure credential collection flow, stores the API key and subdomain in its encrypted vault, and routes all subsequent calls. You never see the key.
if connected_account.status != "ACTIVE":
link = actions.get_authorization_link(
connection_name="freshdesk",
identifier="user_fd_123"
)
# Redirect user → Scalekit-managed credential entry
# Scalekit captures and vaults the API key + subdomain
return redirect(link.link)
Credential management is automatic
After the user completes authorization, Scalekit stores their Freshdesk API key and subdomain in its
encrypted vault and the connected account moves to ACTIVE. If a user resets their API key in Freshdesk,
the account moves to REVOKED — no silent 401s on your end. Check account.status before critical
operations and prompt re-authorization if needed.
Enterprise branding: Bring Your Own Credentials
For deployments where the authorization flow should carry your product’s branding, configure custom
domain settings in the Scalekit dashboard. Credential management stays fully handled.
Already have credentials?
Calling Freshdesk
4 Calling Freshdesk: What your agent writes
With the connected account active, your agent calls Freshdesk actions using execute_tool. Name the tool, pass parameters. Scalekit handles subdomain routing, credential injection, and response parsing.
Create a support ticket
Open a new ticket on behalf of a requester. Scalekit routes the call to the correct subdomain automatically. Either email or requester_id is required to identify the requester.
result = actions.execute_tool(
identifier="user_fd_123",
tool_name="freshdesk_ticket_create",
tool_input={
"subject": "Login issue after password reset",
"description": "
User unable to log in after resetting password.
",
"email": "sarah@acmecorp.com",
"priority": 2, # 1=Low, 2=Medium, 3=High, 4=Urgent
"status": 2, # 2=Open, 3=Pending, 4=Resolved, 5=Closed
"tags": ["auth", "login"]
}
)
# Returns the created ticket with its ID, timestamps, and assigned agent
List open tickets in a queue
Retrieve a filtered ticket list. Use filter to scope to a named queue and include to embed requester and stats in one call.
result = actions.execute_tool(
identifier="user_fd_123",
tool_name="freshdesk_tickets_list",
tool_input={
"filter": "new_and_my_open",
"include": "requester,stats",
"per_page": 30
}
)
# Returns list of open tickets with requester info and SLA stats embedded
Reply to a ticket
Post a public reply to a ticket conversation. The reply appears in the customer’s email thread as sent by the connected agent.
result = actions.execute_tool(
identifier="user_fd_123",
tool_name="freshdesk_tickets_reply",
tool_input={
"ticket_id": 42817,
"body": "
Hi Sarah, we've reset your session — please try logging in again.
",
"cc_emails": ["manager@acmecorp.com"]
}
)
# Reply is posted and visible to the customer immediately
Get a ticket with full context
Retrieve a specific ticket with conversations, requester info, and SLA stats embedded in one call using the include parameter.
result = actions.execute_tool(
identifier="user_fd_123",
tool_name="freshdesk_ticket_get",
tool_input={
"ticket_id": 42817,
"include": "conversations,requester,stats"
}
)
# { "id": 42817, "subject": "...", "conversations": [...], "stats": {...} }
Framework wiring
5 Wiring into your agent framework
Scalekit’s tools load directly into LangChain. The agent decides what to call; Scalekit handles credential routing on every invocation. No API key plumbing in your agent logic.
from langchain_anthropic import ChatAnthropic
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from scalekit.langchain import get_tools
fd_tools = get_tools(
connection_name="freshdesk",
identifier="user_fd_123"
)
prompt = ChatPromptTemplate.from_messages([
("system", "You are a support triage assistant. Use the available tools to manage Freshdesk tickets and respond to customers."),
MessagesPlaceholder("chat_history", optional=True),
("human", "{input}"),
MessagesPlaceholder("agent_scratchpad"),
])
agent = create_tool_calling_agent(ChatAnthropic(model="claude-sonnet-4-6"), fd_tools, prompt)
result = AgentExecutor(agent=agent, tools=fd_tools).invoke({
"input": "Show me all open high-priority tickets and draft a reply for ticket #42817"
})
Other frameworks supported
Tool reference
All 10 Freshdesk tools
Grouped by object and capability. Your agent calls tools by name — no API wrappers to write.
Create a new ticket with subject, description, requester, priority, status, tags, and custom fields
Retrieve a specific ticket by ID, with optional embedding of conversations, requester, company, and SLA stats
Update ticket fields — priority, status, assignment, tags, custom fields. Cannot update subject or description of outbound tickets.
List tickets with filters (new_and_my_open, watching, spam, deleted), pagination up to 100 per page, and optional embedding
Post a public reply to a ticket conversation, with optional CC and BCC recipients
Create a contact with name, email, phone, job title, company association, tags, and custom fields
Create a new helpdesk agent with email, role IDs, ticket scope, group assignment, and agent type
Permanently delete an agent by ID. Irreversible — removes all helpdesk access.
List agents with filtering by email, phone, and state (fulltime/occasional), with pagination up to 100 per page
Retrieve all available roles in the helpdesk with IDs, names, and permission descriptions — needed when creating or updating agents
Connector notes
Freshdesk-specific behavior
Agent role determines what the API will permit
Freshdesk enforces role-based access at the API level. If the connected agent has a restricted role,
API calls that exceed their permissions — such as replying to tickets or accessing admin endpoints —
will return a 403. This is Freshdesk’s design, not a Scalekit error. Use freshdesk_roles_list to
verify role capabilities before building workflows that depend on specific permissions.
Subdomain routing is handled automatically
Every Freshdesk helpdesk runs on a unique subdomain (yourcompany.freshdesk.com). Scalekit captures
and stores the correct subdomain during authorization and routes all API calls accordingly. You never
need to manage or pass subdomain strings in your agent code.
API rate limits are account-wide, not per-agent
Freshdesk rate limits apply per account, not per API key. On trial accounts the default is 50
calls/minute; paid plans have higher limits but they are shared across all agents and integrations.
If you are calling Freshdesk on behalf of many agents simultaneously, monitor rate limit response
headers and implement backoff — even invalid requests count toward the limit.
Infrastructure decision
Why not build this yourself
Freshdesk’s API key authentication looks simpler than OAuth. It isn’t. Here’s what you’re actually signing up for:
PROBLEM 01
Securely collecting API keys from users — a friction-heavy UX that most users can’t complete without documentation or hand-holding
PROBLEM 02
Encrypted, multi-tenant credential storage where one user’s API key is provably inaccessible to another — not just a database column with restricted access
PROBLEM 03
Subdomain routing logic that correctly maps each user to their company’s Freshdesk instance without configuration drift across tenants
PROBLEM 04
Detecting API key revocation or reset and surfacing it as a clean REVOKED state — not as a silent 401 that’s hard to trace in production
That’s one connector. Your agent product will eventually need Salesforce, Gmail, Slack, HubSpot, and whatever else your customers ask for. Each has its own auth model, its own failure modes, its own credential lifecycle.
Scalekit maintains every connector. You maintain none of them.