Skip to main content
This guide shows how to build an integration workflow in Phrase Orchestrator: a workflow that receives an event from a third-party system, fetches content through the Connectors API, translates it with Phrase Language AI or Phrase TMS, and sends the translation back. The examples use the Knak integration, but the same building blocks apply to any connector that exposes download, upload, and (optionally) preview operations.

When to use this pattern

Use an Orchestrator integration workflow when a third-party system can notify Phrase about new content (usually through a webhook) and you want translation to happen automatically, without anyone exporting or importing files by hand. There are two common shapes: The rest of this guide builds the machine translation pattern step by step, then shows how the TMS pattern differs.

Prerequisites

Key concepts

Use the dedicated Phrase Connector API actions wherever one exists for your connector. They authenticate with the Connectors API for you, so you don’t need a token step. Search the action picker for the connector’s name (for example knak): several connectors have an action with the same name, such as Upload raw content synchronously. Phrase Language AI has no actions yet, so its calls use Send HTTP Request v2.

The example workflow

The machine translation workflow for Knak looks like this. Each block is explained in the steps below.

1. Receive the event with a webhook trigger

  1. In the workflow editor, open Triggers, search for webhook, and add the Webhook trigger.
  2. Copy the trigger’s webhook URL and register it in the third-party system. For Knak this is a webhook for the translation_request.created event.
  3. Open Edit webhook and paste an example payload. Orchestrator uses it as a schema, so later steps can pick fields from the trigger. Without it, the trigger’s fields aren’t available in the field picker.
An example Knak payload, with only the fields the workflow uses:
A workflow only receives webhook calls while it is published. To test before connecting the real system, publish the workflow and send the example payload (with a real request ID) to the webhook URL yourself, for example with curl -X POST <webhook-url> -H 'Content-Type: application/json' --data @payload.json. The run then shows up under Executions.
In Knak, a translation request only reaches Phrase (and fires translation_request.created) when Phrase is the translation source. In Request Translation → Source Selection, choose Manual Translation and check that Phrase appears on the confirmation step. AI Translation is translated by Knak itself and sends no event.

2. Extract the values you need

Add a Transform JSON with jq action right after the trigger, so later steps can use short names instead of long payload paths.
  • Input type: JSON
  • Input: {{ @raw }}, with the Webhook trigger as the source
  • jq:
Later steps can now use {{ $.id }}, {{ $.lang }} and {{ $.src }} with this step as their source.

Limit the workflow with a condition

A third-party webhook is often account-wide: it fires for every asset, not just the ones you’re testing with. Add a condition to this jq step so the workflow only continues for the content you expect. For example, on the Conditions tab:
Two easy mistakes here:
  • Use StringEquals for IDs that contain letters. IntegerEquals never matches them.
  • The variable field must contain only the expression. If the field also contains the literal text Webhook (for example Webhook{{ $.data.asset.id }}), the value resolves to Webhook<asset-id> and the condition always fails. The source should appear as a tag next to the field, not as text inside it.

3. Download the source file

Add the connector’s Download raw content synchronously action (search for knak): X-ResponseType: OBJECT makes the action return the file itself (for Knak, an XLIFF 2.0 file) instead of a storage ID, so later steps can use it directly as {{ @file.path }}.
The organizationUid must be the organization the connection belongs to. A mismatch returns 403 with a “connection not accessible” error.

4. Translate with Phrase Language AI

Translating a file with Phrase Language AI takes three calls: log in, start the file translation, and download the result. Language AI has no Orchestrator actions yet, so use Send HTTP Request v2 for all three.

Log in

  • Method: POST
  • URL: https://eu.phrase.com/smt/api/v1/auth/login
  • Body: raw JSON:
The response contains a token. Use it as the bearer token ({{ $.token }}) for the next two calls.
Always include organization.uid. If the user belongs to more than one organization and the body doesn’t say which one, the login returns 401.

Start the file translation

  • Method: POST
  • URL: https://eu.phrase.com/smt/api/v1/fileTranslations
  • Authentication: Bearer token {{ $.token }} (source: the login step)
  • Body: form data with two parts: The metadata part, with the jq step as its source:
To translate with the AI Translation Agent, use an MT profile of that type in mtSettings.profile.uid. Content Groups (contentGroup.uid) only apply to AI Translation Agent profiles; use them for rules such as formality or how to treat links and merge tags. The response contains the translation’s uid.
When you pick a source for a field, the editor may insert {{ @raw }} into the field. In a JSON body that leaves invalid JSON such as ...}"}{{ @raw }}. After selecting the source, check that the field contains only the value you typed.

Wait, then download the result

File translation is asynchronous. The simplest approach is a Sleep action (for example 90 seconds for a short email) before downloading:
  • Method: GET
  • URL: https://eu.phrase.com/smt/api/v1/fileTranslations/{uid}/MT_GENERIC_PRETRANSLATE/{lang}
  • Path parameters: uid = {{ $.uid }} (source: the translation step), lang = {{ $.lang }} (source: the jq step)
  • Header: Accept: application/octet-stream
Placeholders like {uid} in the URL are only replaced when you add a matching entry under path parameters. If one is missing, the request is sent with the literal {uid} in the URL.

5. Validate the translation

Machine translation can occasionally drop or change markup, for example the closing tag of a link. Before sending a translation back, check that every unit kept the same HTML tags and merge tags as its source, and fail the run if not. Add a Convert XML to JSON Format action with the downloaded translation ({{ @file.path }}) as input, and this jq expression:
It compares the tags of each source and target segment (ignoring translated title attributes) and the {{merge tags}}, and fails with the IDs of any units that changed or are empty. A failed run stops before anything is uploaded, so broken content never reaches the third-party system.
If a check fails repeatedly for the same kind of markup, add a rule to the Content Group rather than relaxing the check. For example, a rule such as “Always keep the visible text of every link and its closing </a> tag; translate the link text, never delete it” fixed missing link text for some target languages in the Knak workflow.

6. Send the translation back

Uploading a translation through the Connectors API is a two-step process: store the file, then tell the connector where to put it.

Store the translated file

Add Upload file to file storage synchronously (Phrase Connector API):
  • file: {{ @file.path }} from the step that downloaded the translated file
The output contains the stored file’s uid.

Upload

Add the connector’s Upload raw content synchronously action (search for knak): Each field has its own source, so you don’t need an extra step to combine values. The translation now appears in the third-party system.

Test and publish

  1. Publish the workflow. Webhook-triggered workflows only receive calls while published.
  2. Create a real request in the third-party system, or send an example payload to the webhook URL yourself.
  3. Open Executions, select the run, and click each block to see its Input, Output, Headers and Response Status.
  4. Fix the first failing block, publish again, and resend. Each publish creates a new revision; you can export any revision under Revisions → Export revision as file as a backup.

Changing an existing workflow

  • To add a block between two others, hover over the arrow between them and click +.
  • To remove a block in the middle, delete it, then reconnect the chain: deleting a block attaches the blocks below it directly to the trigger. Drag from the small handle at the bottom of the block above the gap onto the block below it. To remove a leftover connection, click it and then click the circle in its middle.
  • To replace a block, add the new block first, point the next block’s fields to it, and only then delete the old one.
If two workflows listen to the same third-party webhook (for example a machine translation and a TMS version), both run for every event. Give each workflow its own condition, such as a different test asset, or point the third-party webhook at only one of them.

The TMS pattern

When translations need people (translators, reviewers, or a full TMS workflow), replace the Language AI steps with a Phrase TMS project and job. Because human translation takes longer than a single workflow run, this pattern uses two workflows. The native Phrase TMS actions authenticate to TMS for you, so no login step is needed. Workflow 1: request to TMS job, with live preview
  1. Webhook trigger, jq step, and condition, as in steps 1–2.
  2. Download raw content synchronously, as in step 3.
  3. Create project (Phrase TMS action): templateUid set to your project template, name for example Knak {{ $.id }}, sourceLang {{ $.src }}, and one targetLangs item {{ $.lang }} (all from the jq step).
  4. Generate preview package synchronously (Connector API action): the same credentials and path as the download, and X-ResponseType OBJECT, so the action returns the preview ZIP itself.
  5. Upload Job Preview Package (Phrase TMS action): projectUid {{ $.uid }} from the project step, and file {{ @file.path }} from the preview step. The output contains a fileUid.
  6. Create job (Phrase TMS action):
The job then shows a live preview of the content in the TMS editor. Use fileUid from the preview package upload, not its top-level uid.
Put the third-party request ID into the job’s filename (here knak-<id>.xlf). The second workflow needs it to know where to send the translation, and the job filename is the simplest place to carry it.
Configure the file import settings once in the project template rather than per job. For Knak’s XLIFF 2.0 files, enabling the HTML subfilter and converting {{...}} merge tags to Phrase tags (regular expression \{\{[^}]+\}\}) makes HTML and merge tags appear as protected tags in the editor. Changing the template, rather than the organization’s default import settings, keeps the change limited to these projects.
Workflow 2: completed job to translation A second workflow starts when the TMS job is completed, exports and downloads the target file, and sends it back with the same two upload actions as in step 6.
  1. Trigger: on the Triggers tab, search for job and add the Phrase TMS job.status.changed trigger. Add filters so it only runs for completed jobs in the projects this integration creates, for example a project name that starts with Knak. Without filters, the workflow runs for every job status change in the organization.
  2. Job info (Transform JSON with jq, input {{ @raw }} from the trigger): extract the job UID, the project UID, the target language, whether the job is at the last workflow step, and the third-party request ID from the job’s filename (knak-<id>.xlf). The exact field names come from the trigger’s payload; check them in the trigger’s output under Executions.
  3. Prepare target file (Phrase TMS action): projectUid and jobUid from Job info. Add a condition so it only runs when the job is at the last workflow step (for example {{ $.lastStep }} equals true). A job with several workflow steps (translation, then review) is completed once per step, and only the last one should send the translation back.
  4. Wait with a Sleep action. Preparing the target file is asynchronous; a short wait (for example 20 seconds for an email) is enough for small files. Increase it for larger content.
  5. Download target file (Phrase TMS action): projectUid and jobUid from Job info, and asyncRequestId {{ $.asyncRequest.id }} from the prepare step.
  6. Upload file to file storage synchronously: file is {{ @file.path }} from the download step.
  7. Upload raw content synchronously, as in step 6, with path → id and targetLocale from Job info ({{ $.id }}, {{ $.lang }}) and storageId from the file storage step ({{ $.uid }}).
Check that the target language in TMS matches the language code the third-party system expects. Simple codes such as de or fr usually match; regional variants (for example zh_cn in TMS and zh-CN elsewhere) may need mapping in the Job info step.

Calling the Connectors API without an action

If your connector has no Connector API actions yet, call its endpoints with Send HTTP Request v2 and a Connectors API token:
  1. Get a token with an IDM token exchange: POST https://eu.phrase.com/idm/oauth/token, URL-encoded body with grant_type = urn:ietf:params:oauth:grant-type:token-exchange and subject_token = a Phrase platform token stored as a workflow variable. The response contains an access_token.
  2. Call the connector, for example POST https://eu.phrase.com/connectors/<connector>/v1/sync/download-raw-file, with Bearer token {{ $.access_token }} and the same JSON fields the actions use (credentials, path, and so on) as a raw JSON body.
  3. To combine values from several steps into one request body, use Transform JSON with jq v2, which takes two inputs as .[0] and .[1].

Troubleshooting