Skip to main content
Version: Next

FreshService as a Migration Target

FreshService is a common destination for archived Cherwell data. Cortex writes to it over the FreshService REST API, so the target must be configured as a FreshService data source in Cortex before a migration can use it (see Data Sources).


Configuring the target​

The FreshService data source editor has three tabs.

Connection Details​

SettingNotes
Server URLYour FreshService URL, for example https://mycompany.freshservice.com.
API KeyThe API key Cortex authenticates with. It is stored encrypted and is not displayed again after saving.
Workspace IDOptional - the FreshService workspace the tickets belong to. Leave blank for a single-workspace account. See Workspaces.
Test ConnectionConfirms Cortex can reach the instance with the key you entered.
Normalize rich-text HTML on writeOn by default - see Rich text below.
Treat 401 Unauthorized as a rate limit (auto-retry)Off by default - see Intermittent 401 responses below.

Workspaces​

FreshService only exposes the global fields plus the fields of the primary workspace on an unscoped request, and it creates tickets in that primary workspace. An account that keeps a business area in its own workspace is therefore invisible to Cortex by default: the tickets table offers only the global category fields, and the workspace's own fields cannot be mapped.

Set Workspace ID on the Connection Details tab when the ticket fields you migrate into live in a workspace other than the primary one. Leaving it blank keeps the unscoped behaviour, which is what a single-workspace account needs.

With a workspace set:

  • Ticket list, paged and count requests are scoped to that workspace, and new tickets are created in it. The workspace is only sent when a ticket is created, because FreshService cannot change it afterwards.
  • Import Ticket Fields reads the workspace's own ticket form fields, so its custom and dependent fields become available for mapping.
  • Test Connection probes the scoped endpoint and reports the workspace name, so a wrong id fails outright instead of silently passing.
  • Requesters and agents are account-level in FreshService and are deliberately left unscoped.

The Field Definitions tab warns when the cached field definitions were imported for a different workspace than the Workspace ID currently set - run Import Ticket Fields again to load the new workspace's fields.

Field Definitions​

Cortex reads the ticket fields it can write to from this tab:

  • Import Ticket Fields loads the ticket field definitions from the instance. On plans where the field API is unavailable, define the fields manually instead.
  • Add Manual Field (and Edit Field) let you record a field's Name, Label, Field Type, whether it is Required, and its Lookup Values (choices).
  • Dependent (nested) fields are imported one level at a time. FreshService stores each level of a dependent field as its own ticket key (for example category, then sub_category, then item_category), so Cortex turns each level into its own settable target field with that level's choices as its lookup values, and you map the levels you need individually.
  • Required Field Overrides marks a ticket field as required for migration purposes, so Cortex knows to expect it even when the instance reports it differently.

Populating this tab matters: it is what makes target fields and their choice values available when you build the mapping, and what turns a choice field into a dropdown of accepted values in the Migrate Data screen.

Debug / API Test​

Raw API calls against the instance, for troubleshooting an endpoint or a key.


Intermittent 401 responses​

FreshService can answer 401 Unauthorized for an API key that is working - most often when a ticket is being created - and the very same record migrates successfully when it is retried. By default a 401 is treated as a straight failure, so the record is reported under Records needing attention and nothing retries it for you.

Treat 401 Unauthorized as a rate limit (auto-retry), on the data source's Connection Details tab, makes a 401 take the same path as a rate-limit (429) response:

  • The request is retried automatically up to Max retry attempts, in the data source's Rate Limiting tab, and only while Auto-retry on rate limit is also enabled - a 401 is not retried independently of those settings.
  • Every attempt, retries included, passes through the configured request windows, so retrying a record cannot push the instance past the limits Cortex has been told to respect.
  • No delay is invented for the 401. A retry waits only if FreshService asks for one with Retry-After.
  • If the attempts are used up, the real 401 is reported and the record still appears as a failure, so a genuinely bad key ends up in the same place as before.

Retrying a 401 is safe even on the ticket-creating POST: the request was never authenticated, so it was never processed and cannot produce a duplicate ticket.

Leave the option off unless you are actually seeing intermittent 401s. An invalid or revoked API key also answers 401, and retrying it only delays the error.


Mapping into FreshService​

  • Requesters and agents by email. FreshService does not accept a free-text requester name - it expects an existing requester or agent. Use the Lookup Requester Via Email Address transformation to resolve the ticket's requester from an email address field on the source record, and Lookup User Via Email Address for agent fields. If the address matches nothing on the instance, the mapping's Default value is written instead, so a sensible fallback requester can keep records from being rejected.
  • Opened/closed metadata. Fields such as Opened by, Closed by and the opened/closed date-times are set by FreshService itself, not by the API. If those values matter for the archive, map them into custom ticket fields on the instance and map your source values into those fields instead.
  • Descriptions and resolution notes. These are submitted as HTML, so formatting coming from the source system is preserved rather than flattened to plain text.
  • Dates and date-times. FreshService accepts ISO 8601 only, so Cortex converts date values for you - see below.

Date and date/time values​

The FreshService API accepts ISO 8601 date and date-time values only. A source system that holds a date in another shape - 23/01/2026, an epoch number, a value with padding or quotes around it - would be rejected with an invalid_value error, so Cortex converts date-typed values while it builds the request:

  • A value already in the shape its target field accepts is sent unchanged. Nothing is rewritten and nothing is logged, so a mapping that already works cannot change behaviour.
  • Anything else that can be read as a date is rewritten: a date field receives yyyy-MM-dd (2026-01-23), a date and time field receives the UTC instant yyyy-MM-ddTHH:mm:ssZ.
  • A value that cannot be read as a date is sent as it is, so the API's own validation error still names the field and the value at fault instead of the value being blanked.
  • Padding and the single quotes FreshService's own query syntax uses are never valid on write, so they are stripped and the change is reported.

Conversions are reported in the migration message log when a record is created, and in the Cortex log when a record is updated.

What counts as "already valid" is judged per target field, not as a general "looks like ISO 8601" test, because the API's accepted shape differs by field:

Target fieldSent unchangedConverted
A date-type custom field2026-01-23 onlyan instant bound for it, so 2026-01-23T15:04:05Z becomes 2026-01-23
due_by, fr_due_bya full instant that carries a time zone2026-01-23 becomes 2026-01-23T00:00:00Z; an instant with no zone gets one
planned_start_date, planned_end_dateeither clean shape, because FreshService documents no precision for theman instant with no zone, or a value carrying padding or quotes

The date fields Cortex recognises are the built-in ones above plus every imported custom field whose FreshService field type is a date type. When you import ticket fields, a date custom field is typed as a date rather than as short text, which is also what lets the mapping screen treat its values as dates.

The planned dates keep the source value's own precision - a date stays a date, a date and time stays a date and time - so a time component the API may accept is never silently dropped. If a target field should receive a specific shape, convert it on the mapping with one of the date modifiers in Field Mappings rather than relying on the write path.

A bare number is never read as an epoch on the write path: reading 1769174045 as a date is a mapping author's explicit choice, and doing it implicitly could reinterpret an unrelated number as a date. Use the Format Date modifier with epoch input handling for source fields that hold an epoch.


Rich text and embedded images​

Source systems often store rich text as Word/Office-style HTML: a <style> block with generated class names (cs1A2B…) and <span class="cs1A2B"> wrappers. FreshService's write path strips classes and style blocks, which loses bold text and other formatting, and it drops data: image sources, which leaves broken image boxes.

With Normalize rich-text HTML on write enabled (the default), Cortex rewrites the HTML before it is sent:

  • Simple class rules from the <style> block are inlined into each element's style attribute, so formatting such as bold survives the sanitizer. Existing inline styles win, and complex or descendant selectors are left alone.
  • Embedded data: images are extracted, uploaded to the created ticket as attachments, and the field is saved again with the hosted image URLs. Their data: payloads are removed from the request instead of being sent and discarded.

Normalisation costs no extra API calls unless a field actually contains an embedded image - in that case you will see the extra attachment upload and update entries in the migration log. Turning the option off sends the source HTML exactly as it is.


Attachments​

Because FreshService can accept files, the Migrate Data screen offers Transfer File Attachments As Well? for a FreshService target, which copies each record's attachments to the new ticket. See Running and Monitoring a Migration for where attachment problems are reported.


See also​