FreshDesk Connector
The FreshDesk Connector is a Generic Connector that integrates with the FreshDesk v2 REST API (https://<subdomain>.freshdesk.com/api/v2/) to ingest Tickets, Contacts/Agents (Users & User Profiles), Groups, and Knowledge Base articles (Solution Articles) into Aisera.
Connector Type ID:
232(ExternalSystemTypeEnum.FreshDesk)externalSystemTypeEnumvalue (used in Override Configurations):"FreshDesk"Base URL:
https://<subdomain>.freshdesk.comAPI Version: v2 (
/api/v2/)
FreshDesk is a different product from Fresh Connector (also called "Freshervice"). While both are part of the Freshworks ecosystem and share some API conventions, they have distinct API paths, data structures, and content types.
Authentication Types Supported
FreshDesk currently supports only one authentication method: Basic Authentication with API Key.
Note: In the Integration configuration for FreshDesk there is also an option for OAuth. This is simply a provision for our Admin UI to be proactive if FreshDesk’s API starts offering this option.
Basic Authentication (API Key)
The FreshDesk API uses HTTP Basic Auth with the API key as the username and any non-empty string (e.g. X) as the password. The API key is Base64-encoded together with the dummy password.
To find your API key:
Log into your FreshDesk account.
Click your avatar (top-right) → Profile Settings.
Your API key is shown on the right side under Your API Key.
Setting up in Aisera:
In the Authentication tab of the Data Source, choose Basic.
Enter the API key in the
Usernamefield.Enter any string (e.g.
X) in thePasswordfield.
Global Configuration
FreshDesk Connector is a Generic Connector and therefore most of its functions depend on a JSON Configuration Script that is stored in the database:
{"xml2json":false,"externalSystemTypeEnum":"FreshDesk","supportectContentTypes":["Ticket","Incident","User","UserProfile","UserGroup","KnowledgeArticle"],"contentTypeConfiguration":{"Ticket":{"appliesToChildren":true,"httpContentType":"application/json","acceptContentType":"application/json","pagination":{"type":"nested-call","responseParamMappings":{},"pathParamMappings":{"id":"id"},"nestedConfigParamMappings":{},"nestedCallHasEntries":false,"nestedCallExpandsEntry":true,"nestedCallExpandsEntryWithOnlyFirstResult":false,"nestedCallAppendEntry":true,"nestedCallAppendPath":"comments","nestedCallConfiguration":{"appliesToChildren":true,"httpContentType":"application/json","acceptContentType":"application/json","pagination":{"type":"page","useIncrementalPaging":false,"hasMoreField":false,"hasPageTotalCountField":false,"pageParam":"page","limitParam":"per_page","defaultLimit":100,"defaultOffset":0,"dateFormat":"yyyy-MM-dd''T''HH:mm:ss''Z''","tableEntriesArrayPath":"top","isTableEntryJSONArray":false,"retriesNo":0,"appendTopElement":false,"overrideWithDefaultLimit":false},"pathMappings":[{"path":"/api/v2/tickets/{_id_}/conversations"}],"supportedOperations":["LIST"],"respectRequestPaginationInCount":false,"respectEndDate":false,"respectStartDate":false,"discardEntriesOutOfStartOrEndDate":false,"handleNullDatesConfiguration":false,"handleDefaultDatesConfiguration":false,"nullDateStartHoursBefore":0,"nullDateEndHoursAfter":0,"addElementAsArrayUnderTop":false,"downloadFiles":false,"timezone":"UTC","doNotFailOnNestedCall":false,"isAttachment":false,"isFileDownloadRequest":false,"prefetchConfigurations":[]},"paginationConfig":{"type":"page","useIncrementalPaging":false,"hasMoreField":false,"hasPageTotalCountField":false,"pageParam":"page","limitParam":"per_page","defaultLimit":100,"defaultOffset":0,"dateFormat":"yyyy-MM-dd''T''HH:mm:ss''Z''","parameters":[{"field":"updated_since","operator":"eq","value":"{_startDate_}"}],"tableEntriesArrayPath":"top","isTableEntryJSONArray":false,"retriesNo":0,"appendTopElement":false,"overrideWithDefaultLimit":false},"useParallelCalls":false,"sleepOnZeroResults":false,"zeroResultsRetries":0,"zeroResultsRetryInterval":0,"payloadFilterMappings":{},"enrichParamMappings":{},"enrichAppend":false,"nestedResponseFilterConfigurations":[],"copyDateFiltersFromExternalRequest":false,"defaultLimit":0,"defaultOffset":0,"isTableEntryJSONArray":false,"retriesNo":0,"appendTopElement":false,"overrideWithDefaultLimit":false},"pathMappings":[{"path":"/api/v2/tickets?include=description&order_by=updated_at&order_type=desc","pathByKey":"/api/v2/tickets/{_key_}"}],"supportedOperations":["LIST","GET","CREATE","UPDATE","DELETE"],"operations":[{"operation":"UPDATE","headers":{},"comments":{"idPath":"id","commentsPath":"comments","removeCommentsFromPayload":true,"contentTypeConfiguration":{"appliesToChildren":true,"httpContentType":"application/json","acceptContentType":"application/json","pathMappings":[{"path":"/api/v2/tickets/{_id_}/notes"}],"supportedOperations":["CREATE"],"respectRequestPaginationInCount":false,"respectEndDate":false,"respectStartDate":false,"discardEntriesOutOfStartOrEndDate":false,"handleNullDatesConfiguration":false,"handleDefaultDatesConfiguration":false,"nullDateStartHoursBefore":0,"nullDateEndHoursAfter":0,"addElementAsArrayUnderTop":false,"downloadFiles":false,"timezone":"UTC","doNotFailOnNestedCall":false,"isAttachment":false,"isFileDownloadRequest":false,"prefetchConfigurations":[]}},"useFormPayload":false,"useBinaryPayload":false,"customClose":false,"returnGetTableEntryOnClose":false,"customAddComment":false,"useCreatePathInUpdate":false,"listEntryJoltTransforms":[]},{"operation":"GET","httpMethod":"get","headers":{},"enrich":{"listConfiguration":{"responseParamMappings":{},"pathParamMappings":{"id":"id"},"nestedConfigParamMappings":{},"nestedCallHasEntries":false,"nestedCallExpandsEntry":true,"nestedCallExpandsEntryWithOnlyFirstResult":false,"nestedCallAppendEntry":true,"nestedCallAppendPath":"comments","nestedCallConfiguration":{"appliesToChildren":true,"httpContentType":"application/json","acceptContentType":"application/json","pagination":{"type":"page","useIncrementalPaging":false,"hasMoreField":false,"hasPageTotalCountField":false,"pageParam":"page","limitParam":"per_page","defaultLimit":100,"defaultOffset":0,"dateFormat":"yyyy-MM-dd''T''HH:mm:ss''Z''","tableEntriesArrayPath":"top","isTableEntryJSONArray":false,"retriesNo":0,"appendTopElement":false,"overrideWithDefaultLimit":false},"pathMappings":[{"path":"/api/v2/tickets/{_id_}/conversations"}],"supportedOperations":["LIST"],"respectRequestPaginationInCount":false,"respectEndDate":false,"respectStartDate":false,"discardEntriesOutOfStartOrEndDate":false,"handleNullDatesConfiguration":false,"handleDefaultDatesConfiguration":false,"nullDateStartHoursBefore":0,"nullDateEndHoursAfter":0,"addElementAsArrayUnderTop":false,"downloadFiles":false,"timezone":"UTC","doNotFailOnNestedCall":false,"isAttachment":false,"isFileDownloadRequest":false,"prefetchConfigurations":[]},"paginationConfig":{"type":"page","useIncrementalPaging":false,"hasMoreField":false,"hasPageTotalCountField":false,"pageParam":"page","limitParam":"per_page","defaultLimit":100,"defaultOffset":0,"dateFormat":"yyyy-MM-dd''T''HH:mm:ss''Z''","parameters":[{"field":"updated_since","operator":"eq","value":"{_startDate_}"}],"tableEntriesArrayPath":"top","isTableEntryJSONArray":false,"retriesNo":0,"appendTopElement":false,"overrideWithDefaultLimit":false},"useParallelCalls":false,"sleepOnZeroResults":false,"zeroResultsRetries":0,"zeroResultsRetryInterval":0,"payloadFilterMappings":{},"enrichParamMappings":{},"enrichAppend":false,"nestedResponseFilterConfigurations":[],"copyDateFiltersFromExternalRequest":false,"defaultLimit":0,"defaultOffset":0,"isTableEntryJSONArray":false,"retriesNo":0,"appendTopElement":false,"overrideWithDefaultLimit":false},"idPath":"id","returnRequestPayload":true},"useFormPayload":false,"useBinaryPayload":false,"customClose":false,"returnGetTableEntryOnClose":false,"customAddComment":false,"useCreatePathInUpdate":false,"listEntryJoltTransforms":[]}],"respectRequestPaginationInCount":true,"respectEndDate":false,"respectStartDate":false,"discardEntriesOutOfStartOrEndDate":false,"itemDatePath":"updated_at","itemDateFormat":"yyyy-MM-dd''T''HH:mm:ss''Z''","handleNullDatesConfiguration":true,"handleDefaultDatesConfiguration":true,"nullDateStartHoursBefore":0,"nullDateEndHoursAfter":0,"addElementAsArrayUnderTop":false,"downloadFiles":false,"timezone":"UTC","doNotFailOnNestedCall":false,"isAttachment":false,"isFileDownloadRequest":false,"prefetchConfigurations":[]},"User":{"appliesToChildren":true,"httpContentType":"application/json","acceptContentType":"application/json","pagination":{"type":"page","useIncrementalPaging":false,"hasMoreField":false,"hasPageTotalCountField":false,"pageParam":"page","limitParam":"per_page","defaultLimit":100,"defaultOffset":0,"dateFormat":"yyyy-MM-dd''T''HH:mm:ss''Z''","parameters":[{"field":"updated_since","operator":"eq","value":"{_startDate_}"}],"tableEntriesArrayPath":"top","isTableEntryJSONArray":false,"retriesNo":0,"appendTopElement":false,"overrideWithDefaultLimit":false},"pathMappings":[{"path":"/api/v2/contacts","pathByKey":"/api/v2/contacts/{_key_}"}],"supportedOperations":["LIST","GET"],"respectRequestPaginationInCount":false,"respectEndDate":false,"respectStartDate":false,"discardEntriesOutOfStartOrEndDate":false,"itemDatePath":"updated_at","itemDateFormat":"yyyy-MM-dd''T''HH:mm:ss''Z''","handleNullDatesConfiguration":false,"handleDefaultDatesConfiguration":false,"nullDateStartHoursBefore":0,"nullDateEndHoursAfter":0,"addElementAsArrayUnderTop":false,"downloadFiles":false,"timezone":"UTC","doNotFailOnNestedCall":false,"isAttachment":false,"isFileDownloadRequest":false,"prefetchConfigurations":[]},"UserProfile":{"appliesToChildren":true,"httpContentType":"application/json","acceptContentType":"application/json","pagination":{"type":"page","useIncrementalPaging":false,"hasMoreField":false,"hasPageTotalCountField":false,"pageParam":"page","limitParam":"per_page","defaultLimit":100,"defaultOffset":0,"dateFormat":"yyyy-MM-dd''T''HH:mm:ss''Z''","parameters":[{"field":"updated_since","operator":"eq","value":"{_startDate_}"}],"tableEntriesArrayPath":"top","isTableEntryJSONArray":false,"retriesNo":0,"appendTopElement":false,"overrideWithDefaultLimit":false},"pathMappings":[{"path":"/api/v2/contacts","pathByKey":"/api/v2/contacts/{_key_}"}],"supportedOperations":["LIST","GET"],"respectRequestPaginationInCount":false,"respectEndDate":false,"respectStartDate":false,"discardEntriesOutOfStartOrEndDate":false,"itemDatePath":"updated_at","itemDateFormat":"yyyy-MM-dd''T''HH:mm:ss''Z''","handleNullDatesConfiguration":false,"handleDefaultDatesConfiguration":false,"nullDateStartHoursBefore":0,"nullDateEndHoursAfter":0,"addElementAsArrayUnderTop":false,"downloadFiles":false,"timezone":"UTC","doNotFailOnNestedCall":false,"isAttachment":false,"isFileDownloadRequest":false,"prefetchConfigurations":[]},"Incident":{"appliesToChildren":true,"httpContentType":"application/json","acceptContentType":"application/json","pagination":{"type":"nested-call","responseParamMappings":{},"pathParamMappings":{"id":"id"},"nestedConfigParamMappings":{},"nestedCallHasEntries":false,"nestedCallExpandsEntry":true,"nestedCallExpandsEntryWithOnlyFirstResult":false,"nestedCallAppendEntry":true,"nestedCallAppendPath":"comments","nestedCallConfiguration":{"appliesToChildren":true,"httpContentType":"application/json","acceptContentType":"application/json","pagination":{"type":"page","useIncrementalPaging":false,"hasMoreField":false,"hasPageTotalCountField":false,"pageParam":"page","limitParam":"per_page","defaultLimit":100,"defaultOffset":0,"dateFormat":"yyyy-MM-dd''T''HH:mm:ss''Z''","tableEntriesArrayPath":"top","isTableEntryJSONArray":false,"retriesNo":0,"appendTopElement":false,"overrideWithDefaultLimit":false},"pathMappings":[{"path":"/api/v2/tickets/{_id_}/conversations"}],"supportedOperations":["LIST"],"respectRequestPaginationInCount":false,"respectEndDate":false,"respectStartDate":false,"discardEntriesOutOfStartOrEndDate":false,"handleNullDatesConfiguration":false,"handleDefaultDatesConfiguration":false,"nullDateStartHoursBefore":0,"nullDateEndHoursAfter":0,"addElementAsArrayUnderTop":false,"downloadFiles":false,"timezone":"UTC","doNotFailOnNestedCall":false,"isAttachment":false,"isFileDownloadRequest":false,"prefetchConfigurations":[]},"paginationConfig":{"type":"page","useIncrementalPaging":false,"hasMoreField":false,"hasPageTotalCountField":false,"pageParam":"page","limitParam":"per_page","defaultLimit":100,"defaultOffset":0,"dateFormat":"yyyy-MM-dd''T''HH:mm:ss''Z''","parameters":[{"field":"updated_since","operator":"eq","value":"{_startDate_}"}],"tableEntriesArrayPath":"top","isTableEntryJSONArray":false,"retriesNo":0,"appendTopElement":false,"overrideWithDefaultLimit":false},"useParallelCalls":false,"sleepOnZeroResults":false,"zeroResultsRetries":0,"zeroResultsRetryInterval":0,"payloadFilterMappings":{},"enrichParamMappings":{},"enrichAppend":false,"nestedResponseFilterConfigurations":[],"copyDateFiltersFromExternalRequest":false,"defaultLimit":0,"defaultOffset":0,"isTableEntryJSONArray":false,"retriesNo":0,"appendTopElement":false,"overrideWithDefaultLimit":false},"pathMappings":[{"path":"/api/v2/tickets?include=description&order_by=updated_at&order_type=desc","pathByKey":"/api/v2/tickets/{_key_}"}],"supportedOperations":["LIST","GET","CREATE","UPDATE","DELETE"],"operations":[{"operation":"UPDATE","headers":{},"comments":{"idPath":"id","commentsPath":"comments","removeCommentsFromPayload":true,"contentTypeConfiguration":{"appliesToChildren":true,"httpContentType":"application/json","acceptContentType":"application/json","pathMappings":[{"path":"/api/v2/tickets/{_id_}/notes"}],"supportedOperations":["CREATE"],"respectRequestPaginationInCount":false,"respectEndDate":false,"respectStartDate":false,"discardEntriesOutOfStartOrEndDate":false,"handleNullDatesConfiguration":false,"handleDefaultDatesConfiguration":false,"nullDateStartHoursBefore":0,"nullDateEndHoursAfter":0,"addElementAsArrayUnderTop":false,"downloadFiles":false,"timezone":"UTC","doNotFailOnNestedCall":false,"isAttachment":false,"isFileDownloadRequest":false,"prefetchConfigurations":[]}},"useFormPayload":false,"useBinaryPayload":false,"customClose":false,"returnGetTableEntryOnClose":false,"customAddComment":false,"useCreatePathInUpdate":false,"listEntryJoltTransforms":[]},{"operation":"GET","httpMethod":"get","headers":{},"enrich":{"listConfiguration":{"responseParamMappings":{},"pathParamMappings":{"id":"id"},"nestedConfigParamMappings":{},"nestedCallHasEntries":false,"nestedCallExpandsEntry":true,"nestedCallExpandsEntryWithOnlyFirstResult":false,"nestedCallAppendEntry":true,"nestedCallAppendPath":"comments","nestedCallConfiguration":{"appliesToChildren":true,"httpContentType":"application/json","acceptContentType":"application/json","pagination":{"type":"page","useIncrementalPaging":false,"hasMoreField":false,"hasPageTotalCountField":false,"pageParam":"page","limitParam":"per_page","defaultLimit":100,"defaultOffset":0,"dateFormat":"yyyy-MM-dd''T''HH:mm:ss''Z''","tableEntriesArrayPath":"top","isTableEntryJSONArray":false,"retriesNo":0,"appendTopElement":false,"overrideWithDefaultLimit":false},"pathMappings":[{"path":"/api/v2/tickets/{_id_}/conversations"}],"supportedOperations":["LIST"],"respectRequestPaginationInCount":false,"respectEndDate":false,"respectStartDate":false,"discardEntriesOutOfStartOrEndDate":false,"handleNullDatesConfiguration":false,"handleDefaultDatesConfiguration":false,"nullDateStartHoursBefore":0,"nullDateEndHoursAfter":0,"addElementAsArrayUnderTop":false,"downloadFiles":false,"timezone":"UTC","doNotFailOnNestedCall":false,"isAttachment":false,"isFileDownloadRequest":false,"prefetchConfigurations":[]},"paginationConfig":{"type":"page","useIncrementalPaging":false,"hasMoreField":false,"hasPageTotalCountField":false,"pageParam":"page","limitParam":"per_page","defaultLimit":100,"defaultOffset":0,"dateFormat":"yyyy-MM-dd''T''HH:mm:ss''Z''","parameters":[{"field":"updated_since","operator":"eq","value":"{_startDate_}"}],"tableEntriesArrayPath":"top","isTableEntryJSONArray":false,"retriesNo":0,"appendTopElement":false,"overrideWithDefaultLimit":false},"useParallelCalls":false,"sleepOnZeroResults":false,"zeroResultsRetries":0,"zeroResultsRetryInterval":0,"payloadFilterMappings":{},"enrichParamMappings":{},"enrichAppend":false,"nestedResponseFilterConfigurations":[],"copyDateFiltersFromExternalRequest":false,"defaultLimit":0,"defaultOffset":0,"isTableEntryJSONArray":false,"retriesNo":0,"appendTopElement":false,"overrideWithDefaultLimit":false},"idPath":"id","returnRequestPayload":true},"useFormPayload":false,"useBinaryPayload":false,"customClose":false,"returnGetTableEntryOnClose":false,"customAddComment":false,"useCreatePathInUpdate":false,"listEntryJoltTransforms":[]}],"respectRequestPaginationInCount":true,"respectEndDate":false,"respectStartDate":false,"discardEntriesOutOfStartOrEndDate":false,"itemDatePath":"updated_at","itemDateFormat":"yyyy-MM-dd''T''HH:mm:ss''Z''","handleNullDatesConfiguration":true,"handleDefaultDatesConfiguration":true,"nullDateStartHoursBefore":0,"nullDateEndHoursAfter":0,"addElementAsArrayUnderTop":false,"downloadFiles":false,"timezone":"UTC","doNotFailOnNestedCall":false,"isAttachment":false,"isFileDownloadRequest":false,"prefetchConfigurations":[]},"KnowledgeArticle":{"appliesToChildren":true,"httpContentType":"application/json","acceptContentType":"application/json","pagination":{"type":"plain","defaultLimit":0,"defaultOffset":0,"isTableEntryJSONArray":false,"retriesNo":0,"appendTopElement":false,"overrideWithDefaultLimit":false},"pathMappings":[{"path":""}],"supportedOperations":["LIST"],"respectRequestPaginationInCount":false,"respectEndDate":false,"respectStartDate":false,"discardEntriesOutOfStartOrEndDate":false,"responseFilterConfigurations":[],"handleNullDatesConfiguration":false,"handleDefaultDatesConfiguration":false,"nullDateStartHoursBefore":0,"nullDateEndHoursAfter":0,"addElementAsArrayUnderTop":false,"innerAdapterClass":"com.aisera.externalsystems.freshdesk.FreshDeskAdapter","downloadFiles":false,"timezone":"UTC","doNotFailOnNestedCall":false,"isAttachment":false,"isFileDownloadRequest":false,"prefetchConfigurations":[]},"UserGroup":{"appliesToChildren":true,"httpContentType":"application/json","acceptContentType":"application/json","pagination":{"type":"page","useIncrementalPaging":false,"hasMoreField":false,"hasPageTotalCountField":false,"pageParam":"page","limitParam":"per_page","defaultLimit":100,"defaultOffset":0,"dateFormat":"yyyy-MM-dd''T''HH:mm:ss''Z''","tableEntriesArrayPath":"top","isTableEntryJSONArray":false,"retriesNo":0,"appendTopElement":false,"overrideWithDefaultLimit":false},"pathMappings":[{"path":"/api/v2/groups","pathByKey":"/api/v2/groups/{_key_}"}],"supportedOperations":["LIST","GET"],"respectRequestPaginationInCount":false,"respectEndDate":false,"respectStartDate":false,"discardEntriesOutOfStartOrEndDate":false,"handleNullDatesConfiguration":false,"handleDefaultDatesConfiguration":false,"nullDateStartHoursBefore":0,"nullDateEndHoursAfter":0,"addElementAsArrayUnderTop":false,"downloadFiles":false,"timezone":"UTC","doNotFailOnNestedCall":false,"isAttachment":false,"isFileDownloadRequest":false,"prefetchConfigurations":[]}},"contextParameters":{},"basicTokenAuth":false,"startDate":true,"endDate":true,"sleepOnErrors":300000,"retries":10,"rateLimitHeader":"x-ratelimit-remaining","rateLimitRespectNo":5,"rateLimitEntriesSize":5000,"sleepOnRateLimit":60000,"timezone":"UTC","record":false,"useInterval":false,"interval":0,"disableFetchingServiceCatalogCategories":false,"githubRepos":[],"ingestGithubPullRequestCommits":false,"ingestGithubPullRequestReviewComments":false,"ingestGithubPullRequestComments":false,"externalSystemClientV2":{"configuration":{"host":"tenant-server","port":8088,"apiBasePath":"/tenant-server/v2","trustAnySSLCertificate":true}},"secondReading":false,"hasImageProfile":true,"bypassNestedCallsForTestConnection":false,"useRawToken":false,"useRemoteExecutor":false,"useUserCredentials":false,"trustAnySSLCertificate":true,"useImageHandlerWithMetadata":false,"disableArchiving":false,"refreshTokenOn401":false}
In the Data Source’s Configuration wizard there is a tab named “Overrides”. We can place JSON Configurations in that tab to change the Connector’s behavior.
These parameters appear at the top level of the Override Configuration and control the connector's global behavior.
Parameter
Default Value
Description
externalSystemTypeEnum
"FreshDesk"
Must be "FreshDesk" for this connector
startDate
true
Enables incremental ingestion (only fetches entries updated since the last run)
endDate
true
Enables end-date tracking
sleepOnErrors
300000 (5 min)
Sleep time in milliseconds on non-rate-limit errors before retrying
sleepOnRateLimit
60000 (60 sec)
Sleep time in milliseconds when a rate limit response is received
rateLimitHeader
x-ratelimit-remaining
FreshDesk response header used to detect remaining rate limit budget
rateLimitRespectNo
5
Slow down ingestion when remaining API calls drops below this value
rateLimitEntriesSize
5000
Number of entries processed before a rate limit check
retries
10
Max retries on transient (non-rate-limit) errors
timezone
"UTC"
Timezone used for date calculations
hasImageProfile
true
Enables image/avatar support
trustAnySSLCertificate
true
Accepts any SSL certificate (useful for some environments)
Context Parameters
These parameters can be placed inside the contextParameters object within the Override Configuration and allow fine-grained control of the FreshDesk KB adapter:
Key
Type
Default
Description
customerPortalKbUri
"true" / "false"
"false"
When "true", KB article URIs use the customer portal format (/support/solutions/articles/{id}) instead of the agent portal format (/a/support/solutions/articles/{id})
kbPageSize
positive integer string
"100"
Number of items per page for all KB pagination calls (categories, folders, sub-folders, articles). Falls back to 100 if invalid or missing
Example:
{ "externalSystemTypeEnum": "FreshDesk", "contextParameters": { "customerPortalKbUri": "false", "kbPageSize": "100" } }
Learning Functions
Learning Function
Content Type
Adapter Used
Default Endpoint
Learn Tickets
Ticket
Generic (GenericAdapterJSON)
/api/v2/tickets
Learn Users
User
Generic (GenericAdapterJSON)
/api/v2/contacts
Learn User Profiles
UserProfile
Generic (GenericAdapterJSON)
/api/v2/contacts
Learn User Groups
UserGroup
Generic (GenericAdapterJSON)
/api/v2/groups
Learn KBs
KnowledgeArticle
Custom FreshDeskAdapter
/api/v2/solutions/...
Learn Tickets / Incidents
Ticket learning fetches FreshDesk tickets and their full conversation history (replies and notes).
How It Works
The connector lists tickets from
/api/v2/ticketswith incremental filtering viaupdated_since={startDate}.For each ticket, a nested call fetches conversations from
/api/v2/tickets/{id}/conversations.Conversations are appended to the ticket JSON under the
commentsarray.The JOLT transformation converts the integer
priorityfield to a human-readable string.
Important: FreshDesk omits the ticket description from list responses by default. The query parameter include=description must be included in the list call to receive the full description.
API Endpoints
Operation
Method
Endpoint
List tickets
GET
/api/v2/tickets?include=description&order_by=updated_at&order_type=desc&updated_since={startDate}
Get single ticket
GET
/api/v2/tickets/{id}
List conversations
GET
/api/v2/tickets/{id}/conversations
Create ticket
POST
/api/v2/tickets
Update ticket
PUT
/api/v2/tickets/{id}
Delete ticket
DELETE
/api/v2/tickets/{id}
Add note/comment
POST
/api/v2/tickets/{id}/notes
Pagination
Type: Page-based
Page parameter:
pageLimit parameter:
per_page(default: 100)Date format:
yyyy-MM-dd'T'HH:mm:ss'Z'Incremental filter:
updated_since={startDate}Sort order:
order_by=updated_at&order_type=descDate tracking field:
updated_at
Priority Mapping
FreshDesk encodes ticket priority as an integer. The JOLT transformation converts it to a string label:
Integer
String Label
1
Low
2
Medium
3
High
4
Urgent
Note: FreshService uses different labels:
1=Low, 2=Moderate, 3=High, 4=Critical.
Ticket Status Values
Integer
Status
2
Open
3
Pending
4
Resolved
5
Closed
Default Field Mappings
Field Mappings are loaded by default when we create a new Data Source. Admin UI users can edit the field mappings according to each FreshDesk instance’s configuration to better adapt to and process the API’s responses.
Internal Field
FreshDesk Field
Notes
ticketId
id
ticketDisplayId
id
Same as id
ticketTitle
subject
ticketDescription
description
Must use include=description in list call
ticketStatus
status
Integer (see Status Values above)
ticketPriority
priority
Converted to string via JOLT
ticketCategory
category
ticketSubCategory
sub_category
ticketCreationDate
created_at
Format: yyyy-MM-dd'T'HH:mm:ss'Z'
ticketUpdatedAt
updated_at
Format: yyyy-MM-dd'T'HH:mm:ss'Z'
ticketReporterId
requester_id
Ticket submitter
ticketReporterEmail
email
ticketAssignmentGroupId
group_id
Agent group assigned to ticket
ticketAssignedToId
responder_id
Individual assigned agent
ticketUri
(fixed value + JS script)
Set to https://<subdomain>.freshdesk.com/a/tickets/{_ID_}; {_ID_} is replaced at runtime
ticketCommentId
$.comments[*].id
From nested conversations call
ticketCommentText
$.comments[*].body_text
From nested conversations call
ticketCommentCreationDate
$.comments[*].created_at
Format: yyyy-MM-dd'T'HH:mm:ss'Z'
Ticket URI Configuration
The ticketUri field is built in two steps:
A fixed value acts as the URL template (e.g.
https://mycompany.freshdesk.com/a/tickets/{_ID_}).The JS script
freshdesk.ticket.adduri.jsreplaces{_ID_}with the actual ticket ID at runtime.
To configure: find the ticketUri field mapping and update the Fixed Value to match your subdomain.
Use case
Fixed value
Agent portal (default)
https://mycompany.freshdesk.com/a/tickets/{_ID_}
Customer portal
https://mycompany.freshdesk.com/support/tickets/{_ID_}
Filtering Private Comments
By default, FreshDesk tickets include all conversations — both public replies and private notes (marked "private": true). To exclude private notes from ingestion, apply the following Override Document Transformation:
Override Document Transformation JSON (paste into the Data Source's Override Document Transformation field):
{"Ticket":[{"externalSystemTypeId":232,"contentTypeName":"Ticket","name":"Filter Private Comments","transformationType":"SCRIPT","direction":"JSON2Proto","step":"PRE","content":"var comments = jsobject.get(\"comments\");\nvar JSONArray = Java.type('org.json.simple.JSONArray');\nvar filteredComments = new JSONArray();\nvar forEach = Array.prototype.forEach;\n\nif (comments) {\n forEach.call(comments, function(comment) {\n var isPrivate = comment.get(\"private\");\n if (isPrivate === false) {\n filteredComments.add(comment)\n }\n });\n}\njsobject.put(\"comments\", filteredComments);\njsobject;","transformationOrder":1}]}
The underlying JavaScript logic:
var comments = jsobject.get("comments"); var JSONArray = Java.type('org.json.simple.JSONArray'); var filteredComments = new JSONArray(); var forEach = Array.prototype.forEach; if (comments) { forEach.call(comments, function(comment) { var isPrivate = comment.get("private"); if (isPrivate === false) { filteredComments.add(comment) } }); } jsobject.put("comments", filteredComments); jsobject;
This script checks the private field on each conversation entry. Only conversations where private === false (public replies) are retained. All agent-internal notes (private === true) are discarded before processing.
Note: In FreshDesk terminology, ticket "conversations" include both public replies (shown to the requester) and private notes (visible only to agents). The
privateboolean field distinguishes between them.
Learn Users & User Profiles
FreshDesk supports two distinct user types: Contacts (end-users/customers) and Agents (support staff).
Default behavior: The connector ingests Contacts from
/api/v2/contacts.With Override Configuration: Switch to ingesting Agents from
/api/v2/agents.
To ingest User Profiles, you must also enable User Learning in the same Data Source. Selecting only "Learn User Profiles" without "Learn Users" will prevent entries from being properly stored.
Default FreshDesk Type: Contacts
Contacts are end-users who raise support tickets. In FreshDesk, their email and name are top-level fields.
API Endpoints:
Operation
Method
Endpoint
List contacts
GET
/api/v2/contacts?updated_since={startDate}
Get single contact
GET
/api/v2/contacts/{id}
Pagination:
Page-based (
page,per_page=100)Incremental filter:
updated_since={startDate}Date tracking field:
updated_at
Sample Contact JSON:
{ "id": 456, "email": "customer@example.com", "name": "Customer Name", "company_id": 789, "created_at": "2024-01-15T10:00:00Z", "updated_at": "2024-03-20T14:30:00Z" }
Default User Field Mappings (Contacts):
Internal Field
FreshDesk Field
Notes
userExternalId
id
userIdentitiesExternalId
id
userEmail
email
Top-level field
userUsername
email
userFirstName
name
Full name, top-level
Default User Profile Field Mappings (Contacts):
Internal Field
FreshDesk Field
Notes
userProfileIdentityExternalId
id
userProfileIdentityEmail
email
Top-level field
userProfileIdentityFirstName
name
Full name, top-level
userProfileIdentityUserName
email
userProfileRoleName
(fixed)
Always "user"
userProfileWorkInfoDepartmentId
company_id
Single company association
Switching to Agents
To ingest Agents instead of Contacts, apply an Override Configuration that switches the User and UserProfile content type endpoints from /api/v2/contacts to /api/v2/agents.
In the Override Configuration, the User and UserProfile sections should point to /api/v2/agents with tableEntriesArrayPath: "top" (agents are returned as a plain JSON array).
Override Configuration snippet (replace the User and UserProfile blocks in your full Override Configuration):
{"contentTypeConfiguration":{"User":{"appliesToChildren":true,"httpContentType":"application/json","acceptContentType":"application/json","pagination":{"type":"page","useIncrementalPaging":false,"hasMoreField":false,"hasPageTotalCountField":false,"pageParam":"page","limitParam":"per_page","defaultLimit":100,"defaultOffset":0,"dateFormat":"yyyy-MM-dd'T'HH:mm:ss'Z'","tableEntriesArrayPath":"top","isTableEntryJSONArray":false,"retriesNo":0,"appendTopElement":false,"overrideWithDefaultLimit":false},"pathMappings":[{"path":"/api/v2/agents","pathByKey":"/api/v2/agents/{_key_}"}],"supportedOperations":["LIST","GET"],"respectRequestPaginationInCount":false,"respectEndDate":false,"respectStartDate":false,"discardEntriesOutOfStartOrEndDate":false,"handleNullDatesConfiguration":false,"handleDefaultDatesConfiguration":false,"nullDateStartHoursBefore":0,"nullDateEndHoursAfter":0,"addElementAsArrayUnderTop":false,"downloadFiles":false,"timezone":"UTC","doNotFailOnNestedCall":false,"isAttachment":false,"isFileDownloadRequest":false,"prefetchConfigurations":[]},"UserProfile":{"appliesToChildren":true,"httpContentType":"application/json","acceptContentType":"application/json","pagination":{"type":"page","useIncrementalPaging":false,"hasMoreField":false,"hasPageTotalCountField":false,"pageParam":"page","limitParam":"per_page","defaultLimit":100,"defaultOffset":0,"dateFormat":"yyyy-MM-dd'T'HH:mm:ss'Z'","tableEntriesArrayPath":"top","isTableEntryJSONArray":false,"retriesNo":0,"appendTopElement":false,"overrideWithDefaultLimit":false},"pathMappings":[{"path":"/api/v2/agents","pathByKey":"/api/v2/agents/{_key_}"}],"supportedOperations":["LIST","GET"],"respectRequestPaginationInCount":false,"respectEndDate":false,"respectStartDate":false,"discardEntriesOutOfStartOrEndDate":false,"handleNullDatesConfiguration":false,"handleDefaultDatesConfiguration":false,"nullDateStartHoursBefore":0,"nullDateEndHoursAfter":0,"addElementAsArrayUnderTop":false,"downloadFiles":false,"timezone":"UTC","doNotFailOnNestedCall":false,"isAttachment":false,"isFileDownloadRequest":false,"prefetchConfigurations":[]}}}
Agents: In FreshDesk, an agent's email and name are nested inside a contact sub-object, unlike contacts where these are top-level fields. This means field mappings must be updated when switching to Agent ingestion.
Updated Field Mappings for Agents:
To switch to Agent Learning, we need to manually update the field mappings in the Admin UI.
Depending on the API responses of each FreshDesk instance, you might need to make the appropriate adjustments. Not all fields need to be mapped, the last fields in the following table can be ignored if not needed.
Internal Field
Contact Path (default)
Agent Path
Change Reason
userEmail
email
contact.email
Agent email is nested under contact
userUsername
email
contact.email
Same
userFirstName
name
contact.name
Agent name is nested under contact
userProfileIdentityEmail
email
contact.email
Same
userProfileIdentityFirstName
name
contact.name
Same
userProfileIdentityUserName
email
contact.email
Same
userProfileRoleName
"user" (fixed)
"agent" (fixed)
Different role label
userProfileWorkInfoDepartmentId
company_id
department_ids.[0]
Agents have a department ID array
userProfileWorkInfoGroupNames
(not mapped)
group_ids.[*]
Group membership (agents only)
userProfileWorkDepartmentIds
(not mapped)
department_ids.[*]
Full department array (agents only)
userGroupIds
(not mapped)
group_ids.[*]
Group IDs (agents only)
Important: When switching to Agent ingestion you must update all email and name field mappings in both the User and User Profile sections to use the
contact.emailandcontact.namepaths. The fixed value foruserProfileRoleNameshould also be changed from"user"to"agent".
Learn User Groups
FreshDesk Groups map to Aisera User Groups. Groups in FreshDesk represent teams of agents who handle certain types of tickets.
API Endpoints
Operation
Method
Endpoint
List groups
GET
/api/v2/groups
Get single group
GET
/api/v2/groups/{id}
Pagination
Type: Page-based
Page parameter:
pageLimit parameter:
per_page(default: 100)Incremental filter: None — the FreshDesk Groups API does not support
updated_sinceGroups are returned as a plain JSON array, auto-wrapped to
{"top": [...]}by the framework
Field Mappings
Internal Field
FreshDesk Field
Notes
userGroupExternalId
id
userGroupEntityExternalId
id
Same as id
userGroupName
name
FreshDesk vs. FreshService: FreshDesk uses a single
/api/v2/groupsendpoint for all groups. FreshService has separate endpoints:/api/v2/groupsfor agent groups and/api/v2/requester_groupsfor requester groups. FreshDesk does not have a dedicated requester groups endpoint.
Learn Knowledge Base Articles
KB learning uses the custom FreshDeskAdapter class because of the multi-level hierarchical discovery required: Categories → Folders → Sub-folders (recursive) → Articles.
Discovery Flow
GET /api/v2/solutions/categories └─ for each category: GET /api/v2/solutions/categories/{categoryId}/folders └─ for each folder: GET /api/v2/solutions/folders/{folderId}/subfolders ← always called └─ for each subfolder (recursive): GET /api/v2/solutions/folders/{subfolderId}/subfolders GET /api/v2/solutions/folders/{folderId}/articles
The adapter:
Fetches all categories from the solutions API.
For each category, fetches all folders.
For each folder, recursively explores sub-folders (always calling the subfolder endpoint; if empty, no recursion occurs).
For each folder/subfolder, paginates through articles (page by page, default 100 per page).
Before emitting each article, injects enrichment fields (folder, category, visibility, KB URI).
Applies any configured response filter rules.
API Endpoints
Call
Method
Endpoint
List categories
GET
/api/v2/solutions/categories
List folders by category
GET
/api/v2/solutions/categories/{id}/folders
List sub-folders
GET
/api/v2/solutions/folders/{id}/subfolders
List articles by folder
GET
/api/v2/solutions/folders/{id}/articles
Pagination
Each call uses page-based pagination:
Page parameter:
pagePer-page:
per_page=100(configurable viakbPageSizecontext parameter)Stop condition: A page returning fewer items than
per_pageindicates the last pageDuplicate-page guard: If two consecutive pages return identical JSON, pagination stops to prevent infinite loops
In test connection mode, traversal is limited to 5 categories/folders and returns after the first article.
Injected Fields
Before each article is passed to the mapping pipeline, the adapter enriches it with the following fields:
Injected Field
Source
Description
computed_category_folder_path
Built during traversal
Full hierarchical path, e.g. "IT Help > Hardware > Laptops"
folder
Parent folder JSON object
Full folder object embedded in the article
category
Parent category JSON object
Full category object embedded in the folder
folder_visibility
folder.visibility
Integer — see Folder Visibility Values
folder_company_ids
folder.company_ids
List of company IDs; present when folder_visibility == 4
folder_contact_segment_ids
folder.contact_segment_ids
List of contact segment IDs; present when folder_visibility == 6
folder_company_segment_ids
folder.company_segment_ids
List of company segment IDs; present when folder_visibility == 7
kbUri
Constructed by adapter
KB article URL — see KB URI Formats
Article Status Values
Value
Meaning
1
Draft
2
Published
Folder Visibility Values
Value
Meaning
Extra Fields Populated
1
All Users
—
2
Logged In Users
—
3
Agents Only
—
4
Selected Companies
folder_company_ids
5
Bots
—
6
Selected Contact Segments
folder_contact_segment_ids
7
Selected Company Segments
folder_company_segment_ids
KB URI Formats
The adapter constructs and injects a kbUri field for every article:
Audience
URI Format
Default?
Agent portal
https://{instance}.freshdesk.com/a/support/solutions/articles/{id}
Yes
Customer portal
https://{instance}.freshdesk.com/support/solutions/articles/{id}
Set customerPortalKbUri=true in contextParameters
KB Field Mappings
Internal Field
FreshDesk Field
Notes
kbSubject
title
Type: html
kbTitle
title
Type: string
kbBody
description
HTML content of the article
kbURL
kbUri
Injected by adapter
kbDisplayId
id
kbExternalId
id
kbCategory
category_id
Article's top-level category reference
kbTagScope
(fixed)
Always "global"
kbTagKey
tag_keys.[*]
Injected by visibility transformation script
kbTagValue
tag_value_list.[*]
Injected by visibility transformation script
Editing KB Article URLs
Before going live, ensure the kbUri field mapping points to the correct FreshDesk instance. The adapter constructs URLs using config.getUri() (the Data Source base URL), so simply setting the correct base URL (e.g. https://mycompany.freshdesk.com) in the Data Source configuration is sufficient — no additional field mapping edits are needed for the URL format.
Filtering KB Articles by Status or Folder Visibility
By default, the FreshDesk Connector does not apply any filtering. All articles across all statuses and visibility levels are ingested.
You can enable server-side response filtering using the responseFilterConfigurations block inside the KnowledgeArticle section of the Override Configuration. This approach fetches all articles from the API but discards those that do not pass the configured filter rules before passing them to the mapping pipeline.
Example 1 — Ingest only Published articles:
Add the following responseFilterConfigurations in the KnowledgeArticle content type configuration of the original Override Configuration (placed in this section):
"responseFilterConfigurations": [ { "filterKey": "status", "filterValue": "2", "filterOperator": "NOTEQUAL", "filterOperation": "REMOVE" } ]
This filter removes every article whose status is not equal to 2 (Published). Draft articles (status 1) are discarded.
Example 2 — Ingest only Public (All Users) articles:
"responseFilterConfigurations": [ { "filterKey": "folder_visibility", "filterValue": "1", "filterOperator": "NOTEQUAL", "filterOperation": "REMOVE" } ]
This filter removes articles that do not belong to folders with visibility == 1 (All Users).
Example 3 — Ingest only Published articles visible to All Users:
"responseFilterConfigurations": [ { "filterKey": "status", "filterValue": "2", "filterOperator": "NOTEQUAL", "filterOperation": "REMOVE" }, { "filterKey": "folder_visibility", "filterValue": "1", "filterOperator": "NOTEQUAL", "filterOperation": "REMOVE" } ]
Both filters are applied; only articles that are published and in All Users folders are kept.
How it works: The
filterKeyis matched against the article JSON object (including injected fields likefolder_visibility). ThefilterOperator: "NOTEQUAL"means "remove if key's value does not equalfilterValue". Multiple filters are ANDed — an article must pass all filter rules to be retained.
Filtering KB Articles by Folder ID (Override Document Transformation)
For advanced filtering — such as only ingesting articles from specific folders by their numeric ID — use an Override Document Transformation with a JavaScript script.
The script receives each article JSON object as jsobject, examines the folder_id field, and if the folder is not in the approved list, nulls out the id, title, and description fields. This causes the article to be effectively skipped by the mapping pipeline.
Override Document Transformation — Allow-list of Folder IDs:
(Needs editing)
{ "KnowledgeArticle": [ { "externalSystemTypeId": 232, "contentTypeName": "KnowledgeArticle", "name": "Filter by Folder ID", "transformationType": "SCRIPT", "direction": "JSON2Proto", "step": "PRE", "content": "var folderId = jsobject.get(\"folder_id\");\nvar allowedFolderIds = [12001234567, 12001234568, 12001234569];\nvar isAllowed = false;\nfor (var i = 0; i < allowedFolderIds.length; i++) {\n if (allowedFolderIds[i] == folderId) {\n isAllowed = true;\n break;\n }\n}\nif (!isAllowed) {\n print(\"Rejecting folder_id: \" + folderId);\n jsobject.put(\"id\", null);\n jsobject.put(\"title\", null);\n jsobject.put(\"description\", null);\n} else {\n print(\"Accepted folder_id: \" + folderId);\n}\njsobject;", "transformationOrder": 1 } ] }
The underlying JavaScript:
var folderId = jsobject.get("folder_id"); var allowedFolderIds = [12001234567, 12001234568, 12001234569]; var isAllowed = false; for (var i = 0; i < allowedFolderIds.length; i++) { if (allowedFolderIds[i] == folderId) { isAllowed = true; break; } } if (!isAllowed) { print("Rejecting folder_id: " + folderId); jsobject.put("id", null); jsobject.put("title", null); jsobject.put("description", null); } else { print("Accepted folder_id: " + folderId); } jsobject;
Important: Replace the allowedFolderIds array with the actual numeric IDs of the folders you want to Accept. You can find a folder's ID from the FreshDesk admin portal or from the API response.
How it works: Setting
id,title, anddescriptiontonullsignals to the mapping pipeline that the article should be treated as empty/invalid and skipped. Theprint()calls write debug messages to the connector logs.
Fault Tolerance
The adapter continues exploration even when non-fatal errors occur at any level of the hierarchy:
Failure scenario
Recovery
Initial categories fetch fails
Fatal — the run fails completely
Folder-list fetch fails for category X
Category X is skipped; remaining categories continue
Subfolder exploration fails for folder A
Subfolder traversal for A is skipped; folder A's articles are still fetched
Article-list fetch fails for folder C
Folder C is skipped; remaining folders continue
Rate Limiting (Custom Adapter)
The FreshDeskAdapter handles HTTP 429 responses directly:
Reads the
retry-afterheader to determine sleep durationDefault sleep: 60 seconds if
retry-afteris absentMaximum retries: 3 before throwing
AdapterOperationException
API Usage
Summary of all FreshDesk API endpoints used by the connector:
Feature
Endpoint
Method
Notes
List tickets
/api/v2/tickets
GET
include=description, updated_since, pagination
Get ticket
/api/v2/tickets/{id}
GET
Create ticket
/api/v2/tickets
POST
Update ticket
/api/v2/tickets/{id}
PUT
Delete ticket
/api/v2/tickets/{id}
DELETE
List conversations
/api/v2/tickets/{id}/conversations
GET
Nested call per ticket
Add note/comment
/api/v2/tickets/{id}/notes
POST
List contacts
/api/v2/contacts
GET
Default user source
Get contact
/api/v2/contacts/{id}
GET
List agents
/api/v2/agents
GET
Alternative user source
Get agent
/api/v2/agents/{id}
GET
List groups
/api/v2/groups
GET
User groups
Get group
/api/v2/groups/{id}
GET
List KB categories
/api/v2/solutions/categories
GET
KB top level
List KB folders by category
/api/v2/solutions/categories/{id}/folders
GET
List KB sub-folders
/api/v2/solutions/folders/{id}/subfolders
GET
Recursive
List KB articles by folder
/api/v2/solutions/folders/{id}/articles
GET
Paginated
Knowledge Base with ACL Tags
FreshDesk KB folders have a visibility attribute that controls who can read the articles within them. The connector can translate this visibility information into ACL tags on the ingested documents, enabling Aisera to enforce access control at the KB article level.
How It Works
When the
FreshDeskAdapterfetches articles, it injects the following fields from the parent folder:folder_visibility— the integer visibility codefolder_company_ids— company IDs (whenvisibility == 4)folder_contact_segment_ids— contact segment IDs (whenvisibility == 6)folder_company_segment_ids— company segment IDs (whenvisibility == 7)
An Override Document Transformation (JavaScript script) reads these injected fields and populates
tag_keysandtag_value_listarrays on the article JSON.Field mappings then map
tag_keys.[*]→kbTagKeyandtag_value_list.[*]→kbTagValue, which the Aisera system uses for ACL enforcement.
Visibility Tag Mapping
Visibility Value
Meaning
Tags Produced
1
All Users
roles=agent, roles=user
2
Logged In Users
roles=agent, roles=user
3
Agents Only
roles=agent
4
Selected Companies
companyId={id} per company + roles=agent
5
Bots
roles=bot
6
Selected Contact Segments
contactSegmentId={id} per segment + roles=agent
7
Selected Company Segments
companySegmentId={id} per segment + roles=agent
Articles in folders with no folder_visibility value produce no tags (unrestricted).
Override Document Transformation for ACL Tags
{ "KnowledgeArticle": [ { "externalSystemTypeId": 232, "contentTypeName": "KnowledgeArticle", "name": "Parse Visibility ACL Tags", "transformationType": "SCRIPT", "direction": "JSON2Proto", "step": "PRE", "content": "var visibility = jsobject.get(\"folder_visibility\");\nvar tag_keys = new java.util.ArrayList();\ntag_value_list = new java.util.ArrayList();\nif (visibility) {\n var agentOnly = false;\n if (visibility == 3) {\n print(\"Found visibility 3 (Agents)\");\n tag_keys.add(\"roles\");\n tag_value_list.add(\"agent\");\n agentOnly = true;\n } else if (visibility == 4) {\n print(\"Found visibility 4 (Selected Companies)\");\n jsobject.get(\"folder_company_ids\").forEach(function (item, index) {\n tag_keys.add(\"companyId\");\n tag_value_list.add(item);\n });\n } else if (visibility == 5) {\n print(\"Found visibility 5 (Bots)\");\n tag_keys.add(\"roles\");\n tag_value_list.add(\"bot\");\n agentOnly = true;\n } else if (visibility == 6) {\n print(\"Found visibility 6 (Contact Segments)\");\n jsobject.get(\"folder_contact_segment_ids\").forEach(function (item, index) {\n tag_keys.add(\"contactSegmentId\");\n tag_value_list.add(item);\n });\n } else if (visibility == 7) {\n print(\"Found visibility 7 (Company Segments)\");\n jsobject.get(\"folder_company_segment_ids\").forEach(function (item, index) {\n tag_keys.add(\"companySegmentId\");\n tag_value_list.add(item);\n });\n }\n if (tag_keys.size() > 0 && !agentOnly) {\n tag_keys.add(\"roles\");\n tag_value_list.add(\"agent\");\n }\n jsobject.put(\"tag_keys\", tag_keys);\n jsobject.put(\"tag_value_list\", tag_value_list);\n}\njsobject;", "transformationOrder": 1 } ] }
The underlying JavaScript:
var visibility = jsobject.get("folder_visibility"); var tag_keys = new java.util.ArrayList(); tag_value_list = new java.util.ArrayList(); if (visibility) { var agentOnly = false; if (visibility == 3) { print("Found visibility 3 (Agents)"); tag_keys.add("roles"); tag_value_list.add("agent"); agentOnly = true; } else if (visibility == 4) { print("Found visibility 4 (Selected Companies)"); jsobject.get("folder_company_ids").forEach(function (item, index) { tag_keys.add("companyId"); tag_value_list.add(item); }); } else if (visibility == 5) { print("Found visibility 5 (Bots)"); tag_keys.add("roles"); tag_value_list.add("bot"); agentOnly = true; } else if (visibility == 6) { print("Found visibility 6 (Contact Segments)"); jsobject.get("folder_contact_segment_ids").forEach(function (item, index) { tag_keys.add("contactSegmentId"); tag_value_list.add(item); }); } else if (visibility == 7) { print("Found visibility 7 (Company Segments)"); jsobject.get("folder_company_segment_ids").forEach(function (item, index) { tag_keys.add("companySegmentId"); tag_value_list.add(item); }); } if (tag_keys.size() > 0 && !agentOnly) { tag_keys.add("roles"); tag_value_list.add("agent"); } jsobject.put("tag_keys", tag_keys); jsobject.put("tag_value_list", tag_value_list); } jsobject;
Required Field Mappings for ACL Tags
After applying the transformation, add the following field mappings in the Data Source configuration:
Internal Field
FreshDesk Field
Type
kbTagKey
tag_keys.[*]
array element
kbTagValue
tag_value_list.[*]
array element
kbTagScope
(fixed value) "global"
fixed
Note on FreshDesk vs FreshService visibility codes: FreshDesk visibility
4means "Selected Companies" (usesfolder_company_ids). FreshService visibility4means "Departments" (usesfolder_department_ids). These are different ACL concepts — do not copy FreshService ACL scripts directly for FreshDesk.
KB Attachments
FreshDesk Solution Articles support file attachments. Each article in the Freshdesk v2 API response includes an attachments array; each element is an attachment object containing the file metadata and a download URL (which is usually a pre-signed CDN download URL).
FreshDesk Connector is able to ingest Attachments from Solution Articles, but this functionality needs to be enabled, as it is inactive by default.
Sample Article JSON with Attachments
{ "id": 30000302162, "title": "My Article", "description": "<p>Article content</p>", "attachments": [ { "id": 19, "name": "Precautions.pdf", "content_type": "application/pdf", "size": 204800, "attachment_url": "https://s3.amazonaws.com/cdn.freshdesk.com/data/helpdesk/attachments/production/19/original/Precautions.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Signature=..." } ] }
Note on
attachment_url: Freshdesk CDN attachment URLs are pre-signed AWS S3 URLs. They contain embedded AWS credentials (X-Amz-Algorithm,X-Amz-Signature, etc.) and expire after a short window (typically 5 minutes). The adapter detects pre-signed URLs and downloads them without adding anAuthorizationheader to avoid the AWS error"Only one auth mechanism allowed".
Enabling Attachment Ingestion
By default, the connector does not handle article attachments. To enable attachment ingestion, add the fileUploadConfig block to your Override Configuration (either copy paste this, or if there is already an Override Configuration simply copy the fileUploadConfig part in the top level) :
{ "fileUploadConfig": { "inheritFromFieldMappings": true, "fileNameHeader": "Content-Disposition", "fileUploadMetaDataKey": "fileUploadMetadata", "fileOriginAddressPath": "fileUploadMetadata.originAddress", "acceptBothFilesAndJsonEntries": true, "uploadToS3": true, "acceptedContentTypes": [ "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "text/plain" ] } }
Content-Type Pre-filter (acceptedContentTypes)
acceptedContentTypes)Before downloading any attachment, the adapter checks the attachment object's content_type field against acceptedContentTypes.
The default value of acceptedContentTypes (if omitted from the Override Configuration) is the platform-wide SUPPORTED_MIME_TYPES list, which includes PDF, Word, Excel, PowerPoint, plain text, CSV, and other common document formats (like the sample json configuration above).
Pre-signed URL Handling
The adapter inspects attachment_url for embedded credential query parameters before deciding how to authenticate the download:
Detected parameter
Scheme
Action
X-Amz-Algorithm, X-Amz-Signature, or X-Amz-Credential
AWS SigV4
Download without Authorization header
AWSAccessKeyId
AWS SigV2
Download without Authorization header
sig and se (both present)
Azure SAS
Download without Authorization header
X-Goog-Signature
GCP Signed URL
Download without Authorization header
None of the above
Plain URL
Download with standard Authorization header
Failsafe: If the no-auth download attempt fails for any reason, the adapter automatically retries once with the standard Authorization header added.
ACL for Attachments
Attachments automatically inherit the ACL tags from their parent article. The same visibility transformation and field mappings described in Knowledge Base with ACL Tags apply to both the article and its attachment entries. No additional configuration is needed.
URL of Attachments
By default, the URI field for the KBs that have been ingested from Attachment files will be the aisera internal S3 URI. Alternatively, you can activate the Use origin url for files download Check Box under the “Ingestion Configuration” Tab of the Data Source Configuration. This will use the Rest API url that the connector used to Download the Attachment, which is not a browser friendly URI.
If you have a patter to produce browser friendly URIs, you can use a JS Custom Script (must be provided through the Override Document Transformations field under the Overrides Tab of the Data Source configuration) to edit the connector’s JSON Entry that is produced for each ingested attachment. Sample json entry that will need to be edited:
{"id":48012345678,"type":1,"category_id":12000001234,"folder_id":12000056789,"title":"How to Reset Your Password","description":"<div>Step-by-step guide...</div>","description_text":"Step-by-step guide...","status":2,"tags":["password","account"],"thumbs_up":10,"thumbs_down":1,"hits":250,"created_at":"2024-01-15T10:00:00Z","updated_at":"2025-03-20T08:30:00Z","attachments":[{"id":81006248278,"name":"guide.pdf","content_type":"application/pdf","size":204800,"attachment_url":"https://cdn.freshdesk.com/data/helpdesk/attachments/production/81006248278/original/guide.pdf"}],"folder":{"id":12000056789,"name":"Account Management","visibility":1,"company_ids":[],"contact_segment_ids":[],"company_segment_ids":[],"category":{"id":12000001234,"name":"General Help"}},"folder_visibility":1,"computed_category_folder_path":"General Help > Account Management","kbUri":"https://yourcompany.freshdesk.com/a/support/solutions/articles/48012345678","file_upload_metadata":{"s3_bucket":"aisera-ingestion-bucket","s3_key":"tenant-uuid/datasource-id/guide.pdf","attachment_id":"81006248278","filename":"guide.pdf","download_url":"https://yourcompany.freshdesk.com/api/v2/attachments/81006248278","attachment_url":"https://cdn.freshdesk.com/data/helpdesk/attachments/production/81006248278/original/guide.pdf","content_type":"application/pdf","size":204800}}
In the above JSON Structure, the JS Script needs to replace the value of the download_urlvalue under the file_upload_metadata object. Editing this value, when the Check Box is enabled, will alter the URI of the ingested KB.
Resource Files
In the Actions Repo, we keep the following resource files:
File
Purpose
File
Purpose
freshdesk/transformations/freshdesk.ticket.pipeline.transform.json2proto.json
JOLT transform: maps FreshDesk integer priority to string label (Low/Medium/High/Urgent)
freshdesk/transformations/freshdesk.ticket.adduri.js
JS script (POST transform): resolves {_ID_} placeholder in ticketUri with the actual ticket ID
freshdesk/transformations/freshdesk.ticket.pipeline.transform.proto2json.json
JOLT transform: maps string priority back to integer and applies field defaults for create/update operations
fresh/transformations/fresh.ticket.pipeline.transform.json2proto.js
JS script (POST transform): extracts resolution text from last comment for closed tickets — shared with FreshService
fresh/transformations/fresh.kb.adduri.js
JS script (POST transform): replaces {ID} placeholder in source URL for KB articles — shared with FreshService
Related content
Last updated
Was this helpful?
