For the complete documentation index, see llms.txt. This page is also available as Markdown.

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)

  • externalSystemTypeEnum value (used in Override Configurations): "FreshDesk"

  • Base URL: https://<subdomain>.freshdesk.com

  • API 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:

  1. Log into your FreshDesk account.

  2. Click your avatar (top-right) → Profile Settings.

  3. 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 Username field.

  • Enter any string (e.g. X) in the Password field.

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

  1. The connector lists tickets from /api/v2/tickets with incremental filtering via updated_since={startDate}.

  2. For each ticket, a nested call fetches conversations from /api/v2/tickets/{id}/conversations.

  3. Conversations are appended to the ticket JSON under the comments array.

  4. The JOLT transformation converts the integer priority field 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: page

  • Limit 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=desc

  • Date 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:

  1. A fixed value acts as the URL template (e.g. https://mycompany.freshdesk.com/a/tickets/{_ID_}).

  2. The JS script freshdesk.ticket.adduri.js replaces {_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 private boolean 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.email and contact.name paths. The fixed value for userProfileRoleName should 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: page

  • Limit parameter: per_page (default: 100)

  • Incremental filter: None — the FreshDesk Groups API does not support updated_since

  • Groups 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/groups endpoint for all groups. FreshService has separate endpoints: /api/v2/groups for agent groups and /api/v2/requester_groups for 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:

  1. Fetches all categories from the solutions API.

  2. For each category, fetches all folders.

  3. For each folder, recursively explores sub-folders (always calling the subfolder endpoint; if empty, no recursion occurs).

  4. For each folder/subfolder, paginates through articles (page by page, default 100 per page).

  5. Before emitting each article, injects enrichment fields (folder, category, visibility, KB URI).

  6. 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: page

  • Per-page: per_page=100 (configurable via kbPageSize context parameter)

  • Stop condition: A page returning fewer items than per_page indicates the last page

  • Duplicate-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 filterKey is matched against the article JSON object (including injected fields like folder_visibility). The filterOperator: "NOTEQUAL" means "remove if key's value does not equal filterValue". 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, and description to null signals to the mapping pipeline that the article should be treated as empty/invalid and skipped. The print() 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-after header to determine sleep duration

  • Default sleep: 60 seconds if retry-after is absent

  • Maximum 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

  1. When the FreshDeskAdapter fetches articles, it injects the following fields from the parent folder:

    • folder_visibility — the integer visibility code

    • folder_company_ids — company IDs (when visibility == 4)

    • folder_contact_segment_ids — contact segment IDs (when visibility == 6)

    • folder_company_segment_ids — company segment IDs (when visibility == 7)

  2. An Override Document Transformation (JavaScript script) reads these injected fields and populates tag_keys and tag_value_list arrays on the article JSON.

  3. Field mappings then map tag_keys.[*]kbTagKey and tag_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 4 means "Selected Companies" (uses folder_company_ids). FreshService visibility 4 means "Departments" (uses folder_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 an Authorization header 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)

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?