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.
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.
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’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).
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} → 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:
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.
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 doesn’t provide:
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:
Don’t pass code/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+redirectUri — the create request fails without it.
You don’t 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 aren’t 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?... — hostPrefix here is Salesforce’s own login-domain prefix, not the TMS host.
Passing the TMS host (e.g. qa.memsource.com) produces a real-looking but wrong domain (qa.memsource.com.salesforce.com) — a browser certificate error, not 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 doesn’t already end in .my (e.g. d3x000002kko8uao-dev-ed), append it yourself (d3x000002kko8uao-dev-ed.my) rather than sending it as-is — only use a bare login/test prefix (no .my) if you’ve 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’re 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/phraseTmsOrganizationName).