> ## Documentation Index
> Fetch the complete documentation index at: https://developers.phrase.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Managing TMS Connectors

> Create, authorize, and troubleshoot TMS connectors to Google Drive, GitHub, Amazon S3, WordPress, Marketo, and other third-party systems.

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.

<Info>Looking to build a brand-new connector type from scratch instead of configuring one of the existing ones? See [Build a TMS Plugin](/en/guides/build-a-tms-plugin/overview). This guide covers only the connectors Phrase already supports.</Info>

## Quickstart

The simplest call that confirms your setup works is listing the connectors your account can already see. Run this before anything else:

```bash theme={null}
curl -H "Authorization: ApiToken <your-token>" \
  https://cloud.memsource.com/web/api2/v1/connectors
```

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](#prerequisites)); a `403` points to your role or access rights (see [Access-denied (403) on connector creation](#access-denied-403-on-connector-creation)).

## Prerequisites

| Requirement                                                                               | How to get it                                                                           |
| ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| A Phrase account with TMS access                                                          | Provided with your Phrase subscription.                                                 |
| Authentication set up                                                                     | See the [TMS Authentication guide](/en/api/tms/latest/authentication).                  |
| The ADMIN or PROJECT\_MANAGER role, plus the "Modify global server settings" access right | Set per user in the TMS UI. The role alone is not enough; the access right is separate. |
| A subscription add-on for the specific connector type (some types only)                   | Handled through your Phrase subscription or billing, not through access rights.         |

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](/en/api/connectors/introduction)** 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](#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:

| Method & path                                   | Purpose                                     |
| ----------------------------------------------- | ------------------------------------------- |
| `GET /api2/v1/connectors`                       | List your connectors                        |
| `POST /api2/v1/connectors`                      | Create a connector                          |
| `GET /api2/v1/connectors/{connectorId}`         | View a connector's configuration            |
| `PATCH /api2/v1/connectors/{connectorId}`       | Edit a connector's configuration            |
| `DELETE /api2/v1/connectors/{connectorId}`      | Delete a connector                          |
| `GET /api2/v1/connectorAsyncTasks`              | Check background sync task status           |
| `GET /api2/v1/connectors/{connectorId}/folders` | Browse what a connector has actually synced |

<Tip>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.</Tip>

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}`).

<Warning>`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.</Warning>

<Note>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.</Note>

## 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`.

<Warning>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](#phrase-connectors-are-the-exception).</Warning>

### The authorization flow

#### 1. Get a one-time state token

```bash theme={null}
POST /api2/v1/connectors/connectorAuthData
```

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

#### 2. Get the authorization page URL

```bash theme={null}
GET /api2/v1/connectorAuthPage/{type}?hostPrefix={tmsHost}
```

* `{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`).

<Warning>**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.</Warning>

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:

```bash theme={null}
GET /api2/v1/connectors/connectorAuthCode/{code}
```

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](/en/api/tms/latest/introduction).

### 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:

```
https://github.com/apps/phrase-github-integrations-app/installations/new
```

**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:

```bash theme={null}
POST /api2/v3/connectors/github2/connect
{ "code": "...", "redirectUri": "..." }
```

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:

```json theme={null}
{ "type": "GITHUB2", "login": "<chosen login>", "tempLocalToken": "<localToken>" }
```

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.

<Tip>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.</Tip>

### 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.

<Warning>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/...`).</Warning>

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.

<Warning>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.</Warning>

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.

<Tip>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.</Tip>

### 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`).

<Warning>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.</Warning>

#### Marketo (`MARKETO`)

<Warning>`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.</Warning>

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:

```
https://<your-site>/wp-admin/admin.php?page=memsource-connector
```

On that page, click **"Show Connector settings"** and copy the **"Phrase TMS Connector authentication token"**, plus the username and password shown there.

<Warning>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.</Warning>

#### 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`)

<Warning>`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.</Warning>

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](#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](https://status.phrase.com) to rule out a platform incident before deeper debugging.
* For Phrase-side questions (roles, access rights, subscription add-ons, API behavior), contact [Phrase support](https://support.phrase.com).
* 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

<CardGroup cols={2}>
  <Card title="Automate project creation" icon="folder-plus" href="/en/guides/managing-automated-project-creation/overview">
    Have a working connector watch its remote storage and create translation projects automatically.
  </Card>

  <Card title="Build a TMS Plugin" icon="wrench" href="/en/guides/build-a-tms-plugin/overview">
    Build a new connector type from scratch when none of the supported systems fit.
  </Card>
</CardGroup>

***

*Last updated for the TMS API as documented in the [API reference](/en/api/tms/latest/introduction). Track API changes in the [changelog](/en/changelog).*
