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

# OAuth Connector Setup

> Authorize and create OAuth-based TMS connectors (Google Drive, GitHub, Box, Salesforce, and others).

Many connector types authenticate via OAuth 2.0 rather than a plain username/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+token (personal access token), `MARKETO` uses an API key/secret pair, and `OPTIMIZELY`/`TRIDION` use OIDC client-credentials with no user redirect at all. `PHRASE` (the connector to Phrase Strings) also isn't OAuth, despite having a `code`-shaped field — see the note at the end of this page.</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'll 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` doesn't 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 — e.g. `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}` → the token from step 1.
* `{redirectUri}` → `<TMS host>/web/connector/receiveConnectorAuthCode` (this exact path is correct for every provider — don't vary it, and don't 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 doesn't match what's registered for that specific OAuth app — that's 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 — but 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 don't 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 doesn't 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/account to connect. Then create the connector with:

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

Don't pass `code`/`redirectUri` directly to the create call for `GITHUB2` — that request shape is for classic `GITHUB` connectors only.

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