Skip to main content
Manage the full lifecycle of a Phrase Translation Management System (TMS) connector from the API: create a connector to a third-party system like Google Drive, GitHub, Amazon S3, WordPress, or Marketo, authorize it, check its status, and fix it when it breaks.
Looking to build a brand-new connector type from scratch instead of configuring one of the existing ones? See Build a TMS Plugin. This guide covers only the connectors Phrase already supports.

Quickstart

The simplest call that confirms your setup works is listing the connectors your account can already see. Run this before anything else:
A 200 response with a JSON list confirms three things at once: your token is valid, your account can reach the connector endpoints, and you have the access rights to read them. An empty list is still a success; it just means no connectors exist yet. If this call fails, fix it before moving on, because every step below depends on it. A 401 points to the token (see Prerequisites); a 403 points to your role or access rights (see Access-denied (403) on connector creation).

Prerequisites

Authentication setup itself is a link out on purpose, so this list stays short. You only need a valid token and the rights above to begin.

Two different APIs, easy to confuse

Now that you are connected, keep this distinction straight for the rest of the guide. Phrase has two separate API surfaces that both use the word “connector,” and it is easy to reach for the wrong one:
  • The Connectors API only moves file content through a connector that has already been set up: listing, uploading, downloading, and converting files inside a connector’s remote storage. It has no endpoints to create, edit, or delete a connector itself.
  • The TMS API (this guide) owns the connector’s actual lifecycle: creating a new connector, listing your existing ones, viewing or editing a connector’s configuration, deleting it, and checking its sync status. This is where you set up a new Google Drive, GitHub, Amazon S3, WordPress, or Marketo connector.

Worked example: set up a connector end to end

A typical connector setup flow looks like this:
  1. Confirm the connector does not already exist. List existing connectors and check by name and type, so you avoid creating duplicates if a previous attempt errored.
  2. For OAuth-based connector types (Google Drive, GitHub, Box, Salesforce, and others, listed under Set up an OAuth connector), walk the user through authorizing access to the third-party account.
  3. Create the connector with the fields that type requires.
  4. Check the connection status, and fix credentials if it is rejected.
  5. Optionally, monitor sync status and browse what the connector has picked up.

The connector lifecycle endpoints

All connector lifecycle operations live under /api2/v1/connectors on the TMS API:
Before creating a connector, list your existing connectors and check for one with a matching name and type. This avoids accidentally creating duplicates if a previous attempt errored or its result was unclear.
Once a connector is created, keep a reference to its uid. You need it to view, edit, delete, or check the status of that connector later, and it is what you use to link directly to the connector’s page in the TMS UI (/tms/connectors/edit/{uid}).
PATCH /api2/v1/connectors/{connectorId} requires both name and type in the request body, even if you are only changing one field (for example a pure rename). Sending just the changed field (for example {"name": "..."} alone) is rejected with a 400 (“Cannot parse the JSON request”). Read the connector’s current type from a prior GET before editing it.
A connector being successfully created is a different thing from its connection being successfully tested. Creating or editing a connector does not automatically verify its credentials, except for MAGENTO and TYPO3 connector types, which always run a live connection test. For every other type, pass connectionTest=true as a query parameter on POST or PATCH to run one explicitly. Otherwise a connector with entirely wrong credentials can be created successfully and only fail later. If a connection test reports an error status (for example GENERAL_ERROR or UNAUTHORIZED), the connector object still exists, so you usually need to go fix its credentials rather than starting over.

Set up an OAuth connector

Many connector types authenticate via OAuth 2.0 rather than a plain username and password or API key. Setting one up requires walking the end user through a browser-based authorization step before the connector can be created.

Which connector types use OAuth

These types require the authorization flow described below: GITHUB, GITHUB2, BITBUCKET, BOX, ONEDRIVE, SHAREPOINT, GOOGLE, GOOGLE_DRIVE2, SALESFORCE, VERBIS, HUBSPOT, ZENDESK, CONTENTFUL, CONTENTFULENTRYLEVEL, CONTENTFUL2.
Some similarly-named types are not OAuth-based, and calling the authorization endpoint for them will fail. GITLAB and BITBUCKETSERVER use a plain host and token (personal access token), MARKETO uses an API key and secret pair, and OPTIMIZELY and TRIDION use OpenID Connect (OIDC) client-credentials with no user redirect at all. PHRASE (the connector to Phrase Strings) also is not OAuth, despite having a code-shaped field. See PHRASE connectors are the exception.

The authorization flow

1. Get a one-time state token

This returns a one-time state token that correlates your authorization attempt with the eventual callback.

2. Get the authorization page URL

  • {type} must be the exact same ConnectorType enum value you will use to create the connector, not an abbreviation, and not a differently-cased variant. This matters more than it sounds. For example, Google Drive’s correct type is GOOGLE_DRIVE2. GOOGLE_DRIVE does not exist as a type, and GOOGLE is a different, separate connector type. Calling this endpoint with GOOGLE returns 200 successfully, but authorizes against the wrong provider configuration, and will break connector creation later. A 400 response here almost always means the type value is wrong, not that a parameter is missing.
  • hostPrefix should be the bare TMS hostname (host only, no scheme or path, for example qa.memsource.com).
The returned url field is a template, not a ready-to-use link. It contains literal placeholder text ({state} and {redirectUri}) that you must substitute yourself before sending it anywhere. The backend does not fill these in.
Substitute:
  • {state} with the token from step 1.
  • {redirectUri} with <TMS host>/web/connector/receiveConnectorAuthCode (this exact path is correct for every provider; do not vary it, and do not guess an alternative like /tms/...).
If the third-party provider rejects the resulting link with something like “redirect_uri is not associated with this application,” that means this fixed path does not match what is registered for that specific OAuth app. That is a configuration question for whoever manages the app’s registered callback URLs, not something to work around by guessing a different path. Only send the user a fully substituted URL, never one still containing literal {state} or {redirectUri} text, since the provider will reject it outright.

3. Let the user authorize, then poll for the code

Once the user completes the provider’s sign-in and consent screen, their browser is redirected to the callback URL above with the real authorization code attached. That callback page auto-submits the code server-side and the URL reverts within seconds, too fast to copy by hand. Instead, poll for it:
Despite the path parameter’s name, pass the state token from step 1 here, not a code you do not have yet. The parameter is misleadingly named. Poll every few seconds until the response contains a populated code (not null). If the user reports landing on the callback page with no code and no visible error, this is usually a stale or reused state token. Restart from step 1 with a fresh one.

4. Create the connector

Create the connector using the code you polled for, following the type-specific fields documented in the API reference.

GitHub Apps (GITHUB2) need one extra step

GITHUB2 uses GitHub’s newer “GitHub App” model rather than a classic OAuth App, which has two extra requirements beyond the flow above. The Phrase GitHub App must be installed on the user’s GitHub account or organization. OAuth sign-in alone does not install it. Direct users to install it here, ideally alongside the authorization link rather than only after a failure:
An extra API call is required before creating the connector, since its payload needs login and tempLocalToken fields that the standard code exchange does not provide:
This returns { "localToken": "...", "logins": [...] }. If logins has more than one entry, ask the user which GitHub org or account to connect. Then create the connector with:
Do not pass code or redirectUri directly to the create call for GITHUB2. That request shape is for classic GITHUB connectors only.

Google Drive (GOOGLE, GOOGLE_DRIVE2) need oauthClientId too

Both types’ create payload requires an oauthClientId field in addition to code and redirectUri. The create request fails without it.
You do not need to look this up separately or ask the user for it. The same connectorAuthPage call from step 2 above already returned an oauthClientId field in its response. Reuse that exact value verbatim in the create call.

Salesforce (SALESFORCE) specifics

Salesforce differs from every other OAuth type in two ways that are not obvious from the generic flow above.

hostPrefix means something different for Salesforce

For every other type, hostPrefix in step 2 is just the TMS hostname, used to build the TMS-side redirect URI. For Salesforce, connectorAuthPage instead builds the third-party authorize URL as https://{hostPrefix}.salesforce.com/services/oauth2/authorize?..., so hostPrefix here is Salesforce’s own login-domain prefix, not the TMS host.
Passing the TMS host (for example qa.memsource.com) produces a real-looking but wrong domain (qa.memsource.com.salesforce.com), a browser certificate error rather than a real Salesforce host. Omitting it produces a broken empty-prefix URL (https://.salesforce.com/...).
Salesforce has required “My Domain” (*.my.salesforce.com) for all orgs for years, including every Developer Edition org (any domain ending -dev-ed). If the org identifier you have does not already end in .my (for example d3x000002kko8uao-dev-ed), append it yourself (d3x000002kko8uao-dev-ed.my) rather than sending it as-is. Only use a bare login or test prefix (no .my) if you have confirmed the org still uses the legacy non-My-Domain login page. Sending the identifier without .my produces a net::ERR_CERT_COMMON_NAME_INVALID browser error.

salesforcePublishStrategyType is required

The create call fails without this field. Its only valid values are:
  • PUBLISH: translated content is published live immediately.
  • DRAFT: translated content is saved as a draft, not published live.
  • SAME_AS_LATEST_TRANSLATION: mirrors the publish state of the most recent translation.
No other values exist. Plausible-sounding names like KNOWLEDGE or SALESFORCE_KNOWLEDGE are not valid and will be rejected. If you are unsure which to pick, PUBLISH is the safest default, but confirm it against your own publishing requirements.
Collect this value upfront, alongside the other connector fields, rather than as a follow-up after a failed create attempt. Each OAuth authorization code from step 3 is single-use, so a failed create call burns it and requires a full re-authorization for the next attempt.

PHRASE connectors are the exception

The PHRASE connector type (which connects TMS to Phrase Strings, not another TMS instance or a third party) has a code-shaped field but is not part of this OAuth flow. It has no third-party OAuth provider registered, and calling connectorAuthPage for it returns an empty url by design. Its authorization is instead resolved server-side from your own authenticated identity. Attempt creation directly with the connector’s base fields plus its Strings-specific fields (phraseTmsOrganizationId and phraseTmsOrganizationName).

Troubleshooting

Access-denied (403) on connector creation

A 403 or access-denied response when creating a connector has two unrelated causes that share the same generic exception. Always read the exact error message text, since that is the only way to tell them apart.

1. Role and access rights

Creating a connector requires the ADMIN or PROJECT_MANAGER role, and a separate access right called “Modify global server settings”. Having the ADMIN role does not guarantee this right is enabled; it is an independent per-account setting. If your error message is a generic access-denied with no further detail, check this first.

2. Per-connector-type subscription entitlement

Some connector types are gated behind their own subscription add-on, independent of your role or access rights: AEM_PLUGIN, CONTENTFUL, HUBSPOT, OPTIMIZELY, TRIDION, KENTICO_KONTENT, BRAZE_MULTILANG, CONTENTSTACK, MARKETO, and ZENDESK each require a separate Phrase subscription add-on. If the error message literally says something like “<TYPE> is not enabled”, this is not a permissions problem at all. Your organization’s Phrase subscription does not include that connector’s add-on. This is fixable through your subscription or billing, not through any access-rights toggle.
If you can successfully create one connector type but a different type fails with an access-denied error, that is a strong signal you are looking at case 2, not case 1. A role or rights problem would affect every connector type equally.

Per-connector-type field quirks

The generic connector-creation schema only documents the common base fields (name, type, sourceUrl, defaultRemoteFolder, encodedDefaultRemoteFolder, commitMessage). It does not reliably surface each connector type’s own extra fields. A handful of types have field names that do not match what you would naturally guess.

Amazon S3 (AMAZON_S3)

Fields are exactly apiKey, apiSecret, and amazonIamRole, not AWS’s own naming (accessKeyId and secretAccessKey). There is no region field: the region is embedded directly in the bucket hostname (for example integrations-plugins.qa.eu-west-1.s3.memsource.com).
Sending the wrong field names here does not produce a validation error. The API silently accepts null credentials and only fails later, at test-connection time, with an unhelpful server error that can look like a platform outage. If test-connection fails right after creating an AMAZON_S3 connector, check your field names before assuming anything else is wrong.

Marketo (MARKETO)

marketoConnectorType is required. Creation fails without it. Collect it upfront alongside the credentials, not as a follow-up question after the connector already failed to create.
You need four things to create a Marketo connector: the API key, the API secret, the API identity URL, and how the customer wants “Create translations” to work. Use the TMS UI’s own field labels when asking for credentials (“Marketo API key”, “Marketo API secret”, and “Marketo API identity URL”) rather than Marketo’s native OAuth terminology (“Client ID”, “Client Secret”, “Munchkin ID”). They map directly to the API’s field names, but the host value must be sent as identityURL in the request body. host is a hidden internal field name and is silently ignored if you send it instead. The identity URL follows the format https://<munchkin-id>.mktorest.com/identity, for example https://063-RVK-838.mktorest.com/identity. Find your own instance’s value in Marketo Admin, under Integration then Web Services. The “Create translations” mode field is marketoConnectorType, and its values are not dynamic or static as you might expect. They match the two options shown in the TMS UI:
  • SEPARATED_ASSET (“as separate documents”): translations are created as siblings of the original document, with the locale code appended to the title (for example Black Friday promo [de-de]).
  • SEGMENTED_DYNAMIC_CONTENT (“as segmented dynamic content”): translations are stored within the original document, with each segment associated with a target locale.
Required fields are exactly apiKey, apiSecret, host (sent as identityURL), and marketoConnectorType. Segmentation mapping (marketoSegmentationMapping) is optional at the API level even when using segmented dynamic content mode.

Adobe Experience Manager (AEM_PLUGIN)

Beyond the base fields: host (required), plus optional basicAuthUserName and basicAuthPassword (used specifically for live preview, not the main connection), forcedHttps (boolean), and urlRewriteFind and urlRewriteReplace. Not OAuth-based.

WordPress (WORDPRESS)

WordPress connector credentials are not your normal WordPress admin login. They come from the Phrase or Memsource Connector plugin’s own settings page on the WordPress site:
On that page, click “Show Connector settings” and copy the “Phrase TMS Connector authentication token”, plus the username and password shown there.
If test-connection returns GENERAL_ERROR, check whether the site’s host is actually a public URL first. A host like localhost or a local development port is unreachable from Phrase’s servers entirely, regardless of whether the credentials are correct. You will need a public URL or a tunnel (for example ngrok) for Phrase to reach a local WordPress instance.

GitHub (plain GIT type, credential-based)

If you are connecting to a github.com (or GitHub Enterprise) host using the plain GIT connector type (not GITHUB or GITHUB2), note that GitHub has rejected real account passwords for Git-over-HTTPS since August 2021. The password field must be a Personal Access Token, not your actual account password. An UNAUTHORIZED response for a GitHub-hosted GIT connector is almost always this.

Contentstack (CONTENTSTACK)

contentStackRegion is required in practice even though the create-connector schema does not mark it required. Omitting it passes request validation, but the backend then fails while converting the missing value, surfacing as a generic 500 rather than a validation error. Nothing about that response tells you which field caused it.
Send contentStackRegion alongside contentStackAuthType, apiKey, and sourceLang (the fields the schema does mark required) on every create request. Valid values are NORTH_AMERICA, EUROPE, AZURE_NORTH_AMERICA, AZURE_EUROPE, GCP_NORTH_AMERICA, and CUSTOM. Pick whichever matches where your Contentstack organization is hosted; NORTH_AMERICA is the default region for accounts that have not chosen a specific one. If a CONTENTSTACK create request returns a 500, check for this field before assuming a platform outage.

Types not listed here

For any connector type not covered above, inspect its create-connector request schema directly (use the full schema view in the API reference, not the summarized one) before assuming what fields it needs. A code and redirectUri field pair means it is OAuth-based (see Set up an OAuth connector); anything else (a token, an API key, or host and credentials) means it is not.

Get more help

If a connector is still failing after the checks above:
  • Check the Phrase status page to rule out a platform incident before deeper debugging.
  • For Phrase-side questions (roles, access rights, subscription add-ons, API behavior), contact Phrase support.
  • For third-party-side questions (a rejected redirect_uri, a GitHub App installation, a Salesforce org domain), work with whoever manages that third-party application’s configuration. Phrase support cannot change another provider’s registered OAuth app.

Next steps

Automate project creation

Have a working connector watch its remote storage and create translation projects automatically.

Build a TMS Plugin

Build a new connector type from scratch when none of the supported systems fit.

Last updated for the TMS API as documented in the API reference. Track API changes in the changelog.