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

# Build a TMS Plugin

> Build a Phrase TMS plugin end to end, from authentication through import, monitoring, export, live preview, and production hardening.

Build a plugin that sends source content from a third-party system into Phrase TMS (Translation Management System) for localization, tracks progress, and pulls finished translations back.

A TMS plugin is software that lives in a third-party system and connects it to Phrase TMS. It sends source content for localization, monitors translation progress, and retrieves completed translations into the source system.

<Info>Looking to translate app strings or software copy instead? See the Phrase Strings guides. This guide covers Phrase TMS plugins only.</Info>

## Quickstart

The fastest way to confirm your plugin can reach Phrase TMS is to authenticate and list project templates. A `200` response means your token and base URLs are correct, before you build anything specific to your integration.

Set your base URLs once and reuse them:

```bash theme={null}
PLATFORM_BASE_URL="https://<your-region>.phrase.com"
TMS_BASE_URL="https://<your-tms-host>/web"
```

Exchange your API token for a short-lived access token:

```bash theme={null}
curl --request POST "${PLATFORM_BASE_URL}/idm/oauth/token" \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
  --data-urlencode 'subject_token=YOUR_API_TOKEN'
```

Call one read-only endpoint to confirm the connection:

```bash theme={null}
curl --request GET "${TMS_BASE_URL}/api2/v1/projectTemplates" \
  --header "Authorization: Bearer ${ACCESS_TOKEN}"
```

A `200` response with a list of templates confirms your plugin can reach Phrase TMS. The rest of this guide builds the full plugin flow on top of that.

## Prerequisites

| Requirement                    | Details                                                                                    |
| ------------------------------ | ------------------------------------------------------------------------------------------ |
| Phrase account with TMS access | An account provisioned for Phrase TMS.                                                     |
| Phrase Platform API token      | Used to obtain short-lived access tokens.                                                  |
| Authentication set up          | Token exchange configured. See [Platform authentication](/en/api/platform/authentication). |
| Base URLs for your region      | Platform base URL and TMS API base URL for your tenant.                                    |
| A project template             | At least one TMS project template available in your organization.                          |
| Secure secret storage          | A safe place to hold the API token and runtime secrets.                                    |
| A callback endpoint (optional) | Required only if you use push-based progress handling.                                     |

<Note>Do not hardcode EU endpoints unless your tenant runs on EU infrastructure.</Note>

## Key concepts

These localization-specific terms appear throughout this guide.

| Term                | Definition                                                                                                           |
| ------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Project template    | Reusable configuration used to create projects with standard settings and workflow steps.                            |
| Project             | Container for related translation jobs.                                                                              |
| Job                 | A translation unit for one file and one target language.                                                             |
| Workflow            | Ordered translation and review steps that jobs move through.                                                         |
| `asyncRequest`      | Identifier for an asynchronous API operation that completes later.                                                   |
| Locale              | Source or target language and region identifier used in localization.                                                |
| Machine translation | Automated translation used in workflow steps, often combined with human review.                                      |
| Segmentation        | Splitting content into translatable segments used for progress and quality checks.                                   |
| Translation memory  | Reusable translation database that improves consistency and speed.                                                   |
| Markup language     | Structured text format such as HTML or XML where content and formatting tags must be kept intact during translation. |

These file types are common in plugin integrations.

| File type       | Typical use                                                        |
| --------------- | ------------------------------------------------------------------ |
| XLIFF 1.2 / 2.0 | Standard localization exchange format.                             |
| JSON            | Structured application or content management system (CMS) content. |
| XML             | Structured content and metadata.                                   |
| Markdown        | Documentation and content workflows.                               |
| Plain text      | Simple, non-structured content.                                    |

For import parsing behavior, see [file import settings](https://support.phrase.com/hc/en-us/sections/5709618056604-File-Import-Settings).

## Build the plugin

A production plugin usually follows this lifecycle:

1. Select source content in the third-party system.
2. Authenticate with Phrase Platform and call TMS APIs.
3. Create a project from a project template.
4. Create one or more jobs by uploading source content.
5. Monitor job progress with webhooks, async status, or job-part polling.
6. Export translated files when jobs complete.
7. Set job status (for example `DELIVERED`) after a successful import.

### 1. Authenticate

Exchange your API token for a short-lived access token and send it as `Authorization: Bearer <token>`. Cache the token and refresh it before expiry, or on a single retry after a `401`. Centralize refresh logic in production to avoid refresh storms under load.

```text theme={null}
CACHE = { token: null, expiresAt: 0 }

function getAccessToken():
  if CACHE.token exists and now < CACHE.expiresAt - safetyWindow:
    return CACHE.token
  tokenResponse = POST ${PLATFORM_BASE_URL}/idm/oauth/token
  CACHE.token = tokenResponse.access_token
  CACHE.expiresAt = now + tokenResponse.expires_in
  return CACHE.token

function authRequest(method, url, body):
  token = getAccessToken()
  response = HTTP(method, url, header={"Authorization": "Bearer " + token}, body=body)
  if response.status == 401:
    CACHE.token = null
    token = getAccessToken()
    response = HTTP(method, url, header={"Authorization": "Bearer " + token}, body=body)
  return response
```

<Warning>If a call returns `401` after a fresh token, the token exchange itself is failing. Check the API token value and that `grant_type` is set to the token-exchange value. Do not retry in a tight loop; surface an actionable auth error instead.</Warning>

Reference: [Platform authentication](/en/api/platform/authentication), [OAuth token endpoint](/en/api/platform/oauth/token-endpoint).

### 2. Import content into TMS

1. List project templates: `GET /api2/v1/projectTemplates`
2. Create a project from a template: `POST /api2/v2/projects/applyTemplate/{templateUid}`
3. Create a job in the project: `POST /api2/v1/projects/{projectUid}/jobs`

```bash theme={null}
curl --request POST "${TMS_BASE_URL}/api2/v2/projects/applyTemplate/${TEMPLATE_UID}" \
  --header "Authorization: Bearer ${ACCESS_TOKEN}" \
  --header 'Content-Type: application/json' \
  --data '{"name":"My Plugin Project"}'
```

<Note>Persist `templateUid`, `projectUid`, `job.uid`, and any returned `asyncRequest.id`. You need these IDs in later steps.</Note>

<Warning>A `404` on create-project usually means the `templateUid` is wrong or the token lacks access to that template. Verify the ID against the list-templates response and confirm the account scope before retrying.</Warning>

Reference: [List project templates](/en/api/tms/latest/project-template/list-project-templates), [Create project from template](/en/api/tms/latest/project/create-project-from-template), [Create job](/en/api/tms/latest/job/create-job).

#### Optional: expose content listing and download endpoints

If your integration uses TMS pull behavior from the third-party system:

* Expose a **list endpoint** that returns selectable content.
* Expose a **download endpoint** that returns encoded content for import.
* Validate auth and return clear HTTP errors for invalid requests.

<Tip>Keep endpoint responses deterministic. Stable IDs and filenames reduce duplicate imports and cut debugging time.</Tip>

#### Optional: workflow extensions

If your lifecycle needs vendor automation and project manager workflow support, add these operations after job creation:

* Assign providers from template: `POST /api2/v1/projects/{projectUid}/applyTemplate/{templateUid}/assignProviders`
* Create analyses by providers: `POST /api2/v1/analyses/byProviders`
* Notify assigned users: `POST /api2/v1/projects/{projectUid}/jobs/notifyAssigned`

Reference: [Assign providers from template](/en/api/tms/latest/project/assigns-providers-from-template), [Create analyses by providers](/en/api/tms/latest/analysis/create-analyses-by-providers), [Notify assigned users](/en/api/tms/latest/job/notify-assigned-users).

### 3. Monitor job progress

Use this priority order:

1. **Webhooks (recommended for production).** Configure webhook subscriptions for job, part, and async lifecycle events. Verify the sender token or header, store events idempotently, acknowledge quickly, and process asynchronously.
2. **Async request status polling.** Poll `GET /api2/v1/async/{asyncRequestId}` for completion when the workflow uses async endpoints.
3. **Job-part polling fallback.** Poll `GET /api2/v1/projects/{projectUid}/jobs/{jobUid}/parts` with backoff and jitter.

#### Callback pattern (alternative to polling)

Many async endpoints support a `callbackUrl`. The callback payload includes async request metadata and the action result.

* Validate the sender, persist the event, and return `200` quickly.
* If the callback URL is unreachable, Phrase retries after 2, 4, 8, 16, and 30 minutes, up to 10 failed retries.
* Callback delivery counts as successful only when your endpoint returns HTTP `200`.

```text theme={null}
POST /callbacks/phrase-async
  verifySignatureOrToken(request)
  event = parseJson(request.body)
  saveEventIdempotently(event.asyncRequest.id)
  enqueueAsyncProcessing(event)
  return 200
```

<Warning>If your endpoint is slow or returns a non-`200` status, Phrase treats delivery as failed and stops retrying after 10 attempts. Acknowledge with `200` first, then process the event asynchronously so slow work never blocks delivery.</Warning>

Reference: [Get asynchronous request](/en/api/tms/latest/async-request/get-asynchronous-request), [Webhooks (support article)](https://support.phrase.com/hc/en-us/articles/5709693398812-Webhooks-TMS).

#### Job status reference

| Status          | Typical phase             | Meaning                                               | Plugin action                          |
| --------------- | ------------------------- | ----------------------------------------------------- | -------------------------------------- |
| `NEW`           | Creation / pre-assignment | Job exists and is not yet accepted                    | Keep monitoring and surface as queued  |
| `ACCEPTED`      | Assignment                | Provider accepted the job                             | Continue monitoring workflow progress  |
| `DECLINED`      | Assignment                | Provider declined the job                             | Reassign provider or notify operator   |
| `COMPLETED`     | Completion                | Translation workflow is complete                      | Start the export flow                  |
| `DELIVERED`     | Post-export               | Target content was exported and imported successfully | Treat as terminal success              |
| `CANCELLED`     | Exception                 | Work was stopped before completion                    | Treat as terminal failure              |
| `REJECTED`      | Exception / QA            | Output was rejected and needs rework                  | Route for rework, do not deliver       |
| `JOB_NOT_READY` | Import exception          | Job creation or import did not complete correctly     | Inspect import errors and retry safely |

### 4. Export translated content

1. Start the async export: `PUT /api2/v3/projects/{projectUid}/jobs/{jobUid}/targetFile`
2. Wait for completion via webhook, callback, or polling.
3. Download the file once ready: `GET /api2/v2/projects/{projectUid}/jobs/{jobUid}/downloadTargetFile/{asyncRequestId}`
4. Import the result into the third-party system.
5. Set the terminal status: `POST /api2/v1/projects/{projectUid}/jobs/{jobUid}/setStatus` with `requestedStatus: DELIVERED`.

```json theme={null}
{
  "requestedStatus": "DELIVERED",
  "notifyOwner": true,
  "propagateStatus": true
}
```

Alternatively, expose a secure endpoint to receive translated payloads pushed from TMS and run your import mapping there.

<Warning>The `asyncRequestId` for download is single-use. If a download fails midway, do not replay the same ID. Start a new export and consume the new ID.</Warning>

Reference: [Download target file async](/en/api/tms/latest/job/download-target-file-async-1), [Download target file by async request](/en/api/tms/latest/job/download-target-file-based-on-async-request), [Edit job status](/en/api/tms/latest/job/edit-job-status).

### Optional: live preview

Live preview renders a visual representation of content in the editor so translators understand context. Use it when text meaning depends on layout, styling, or surrounding content: CMS pages with rich formatting, product or marketing pages with embedded UI copy, or content with many short strings.

**Option A: HTML file localization.** If your source can be represented as HTML, upload HTML directly as the translatable file. This is the simplest setup and gives immediate visual context, but it is not always feasible for CMS models with complex field structures and may need custom conversion logic.

**Option B: preview package via API.** If HTML-as-source is not feasible, upload a preview package and reference it during job creation:

1. Build a ZIP package with one HTML entry point plus relative assets.
2. Upload the package: `POST /api2/v1/projects/{projectUid}/jobPreviewPackage`
3. Use the returned preview file UID in job creation metadata (`jobPreviewPackageFileUidRef` in the Memsource header. See [Plugin metadata](#plugin-metadata)).

```bash theme={null}
curl --request POST "${TMS_BASE_URL}/api2/v1/projects/${PROJECT_UID}/jobPreviewPackage" \
  --header "Authorization: Bearer ${ACCESS_TOKEN}" \
  --form "file=@preview-package.zip"
```

<Warning>Preview uploads fail on malformed ZIP structure, missing assets, incorrect relative paths, or oversized packages. Validate the ZIP and run package integrity checks in your continuous integration pipeline before upload, and regenerate the package when the content schema changes.</Warning>

Reference: [Upload job preview package](/en/api/tms/latest/project/upload-job-preview-package), [Create job](/en/api/tms/latest/job/create-job).

### Optional: continuous updates

For continuous localization, update source content on existing jobs and re-run the workflow:

* Update the source: `POST /api2/v1/projects/{projectUid}/jobs/source`.
* Monitor completion with callbacks, webhooks, or async polling.
* Re-export translated output when the updates finish.

Reference: [Update source](/en/api/tms/latest/job/update-source).

### Plugin metadata

Send plugin metadata in the Memsource header payload when creating jobs:

```json theme={null}
{
  "sourceData": {
    "clientType": "MY_PLUGIN",
    "clientVersion": "1.2.4",
    "hostVersion": "4.5.2"
  }
}
```

`clientType` is required and must be registered with Phrase. `clientVersion` and `hostVersion` are optional.

## Rate limits

* Phrase TMS documents an API limit of **6,000 requests per minute for logged-in users**.
* Async workflows can queue under load, so avoid aggressive status polling.
* Webhooks reduce polling load and should be your primary production signal.

<Note>Use exponential backoff with jitter for retries on `429` and `5xx` responses. Treat the support documentation limits as the source of truth for current values.</Note>

Reference: [Handling API rate limits](/en/api/tms/latest/handling-api-rate-limits), [Handling concurrent API limits](/en/api/tms/latest/handling-concurrent-api-limits), [Phrase TMS limits (support)](https://support.phrase.com/hc/en-us/articles/5784117234972-Phrase-TMS-Limits).

## Production hardening

### Idempotency

Implement idempotency for all create and delivery operations:

| Operation                      | Recommended idempotency key                   |
| ------------------------------ | --------------------------------------------- |
| Create project                 | External content batch ID + template ID       |
| Upload or create job           | Source content checksum + locale + project ID |
| Start export                   | Job ID + source revision                      |
| Download or import target file | Async request ID + target system item ID      |
| Update status (`DELIVERED`)    | Job ID + delivered revision                   |

Build a deterministic operation key before each API call, store it in durable storage with the operation state, and return the stored result instead of replaying when a duplicate key appears.

### Logging

Log these fields for every API call and lifecycle event:

| Field                   | Why it matters                                            |
| ----------------------- | --------------------------------------------------------- |
| `timestamp`             | Event ordering and latency analysis                       |
| `requestId`             | Ties plugin logs to HTTP calls                            |
| `correlationId`         | Traces a single localization flow end to end              |
| `projectUid` / `jobUid` | Project- and job-level troubleshooting                    |
| `asyncRequestId`        | Async lifecycle correlation                               |
| `pluginInstanceId`      | Multi-instance debugging                                  |
| `endpoint` + `method`   | API behavior visibility                                   |
| `statusCode`            | Error and retry analytics                                 |
| `durationMs`            | Performance and service level objective (SLO) measurement |

### Plugin variants

| Capability                | Canonical plugin | Live content plugin | Continuous localization plugin |
| ------------------------- | ---------------- | ------------------- | ------------------------------ |
| Token exchange + refresh  | Must             | Must                | Must                           |
| Project from template     | Must             | Must                | Must                           |
| Webhooks                  | Should           | Must                | Must                           |
| Live preview              | Can              | Must                | Can                            |
| Idempotent source updates | Should           | Must                | Must                           |
| Advanced observability    | Should           | Must                | Must                           |

### Operational hardening checklist

* Retry transient errors (`429`, `5xx`) with exponential backoff and jitter.
* Avoid duplicate create and export actions with idempotency keys or dedupe logic.
* Log correlation IDs, project and job IDs, endpoint, status, and latency.
* Track success and error rates for create, monitor, and export operations.
* Add alerts for repeated failures and webhook delivery issues.
* Add fallback polling for missed webhook events.

### Testing expectations

* Happy path: create, monitor, export, `DELIVERED`.
* Failure path: auth errors, validation errors, rate limits, service errors.
* Resilience path: a missed webhook with polling fallback.
* Update path (if enabled): source updates and a repeated export.

## Error reference

Handle these HTTP status codes. For full payload schemas and the canonical error catalog, see the [TMS API reference](/en/api/tms/latest/introduction).

| Error code | Meaning                               | Recommended handling                                                             |
| ---------- | ------------------------------------- | -------------------------------------------------------------------------------- |
| `400`      | Invalid request payload or parameters | Validate payload shape, required fields, and enum values before retrying.        |
| `401`      | Authentication failed                 | Refresh the access token, retry once, then surface actionable auth guidance.     |
| `403`      | Authenticated but not authorized      | Check role and permissions in Phrase TMS and the account scope.                  |
| `404`      | Referenced resource not found         | Verify IDs (`projectUid`, `jobUid`, template IDs) and the endpoint path version. |
| `429`      | Rate limit reached                    | Back off with jitter, and reduce poll frequency and burst size.                  |
| `500`      | Internal service error                | Retry with bounded backoff, and capture request context for support escalation.  |
| `503`      | Temporary service unavailability      | Retry with bounded backoff, and defer non-critical jobs when possible.           |

Validate file type and size before upload, split very large submissions into smaller batches, and fail fast with actionable feedback when a file is unsupported. See [file import settings and limits](https://support.phrase.com/hc/en-us/sections/5709618056604-File-Import-Settings).

## Endpoint reference

Every endpoint this guide calls, in one place. Paths are relative to `TMS_BASE_URL`, except the token endpoint, which uses `PLATFORM_BASE_URL`. For full request and response schemas, see the [TMS API reference](/en/api/tms/latest/introduction).

| Action                                | Method | Endpoint                                                                           |
| ------------------------------------- | ------ | ---------------------------------------------------------------------------------- |
| Exchange API token for access token   | `POST` | `${PLATFORM_BASE_URL}/idm/oauth/token`                                             |
| List project templates                | `GET`  | `/api2/v1/projectTemplates`                                                        |
| Create project from template          | `POST` | `/api2/v2/projects/applyTemplate/{templateUid}`                                    |
| Create job                            | `POST` | `/api2/v1/projects/{projectUid}/jobs`                                              |
| Assign providers from template        | `POST` | `/api2/v1/projects/{projectUid}/applyTemplate/{templateUid}/assignProviders`       |
| Create analyses by providers          | `POST` | `/api2/v1/analyses/byProviders`                                                    |
| Notify assigned users                 | `POST` | `/api2/v1/projects/{projectUid}/jobs/notifyAssigned`                               |
| Get asynchronous request              | `GET`  | `/api2/v1/async/{asyncRequestId}`                                                  |
| List job parts                        | `GET`  | `/api2/v1/projects/{projectUid}/jobs/{jobUid}/parts`                               |
| Upload job preview package            | `POST` | `/api2/v1/projects/{projectUid}/jobPreviewPackage`                                 |
| Start target file export              | `PUT`  | `/api2/v3/projects/{projectUid}/jobs/{jobUid}/targetFile`                          |
| Download target file by async request | `GET`  | `/api2/v2/projects/{projectUid}/jobs/{jobUid}/downloadTargetFile/{asyncRequestId}` |
| Set job status                        | `POST` | `/api2/v1/projects/{projectUid}/jobs/{jobUid}/setStatus`                           |
| Update source                         | `POST` | `/api2/v1/projects/{projectUid}/jobs/source`                                       |

## Troubleshooting and support

* Check the [Phrase status page](https://status.phrase.com) first when calls fail unexpectedly across the board.
* For Phrase TMS behavior, API access, or account questions, contact [Phrase support](https://support.phrase.com).
* For issues with an AI coding tool or agent you use to build the plugin, use that tool's own support channel. Phrase support cannot debug third-party tools.

## Next steps

* Set up token exchange with [Platform authentication](/en/api/platform/authentication).
* Configure account-level events with the [Webhooks support guide](https://support.phrase.com/hc/en-us/articles/5709693398812-Webhooks-TMS).
* Browse full payload schemas in the [TMS API reference](/en/api/tms/latest/introduction).

## Reference

* [TMS API reference](/en/api/tms/latest/introduction) for full payload schemas.
* [Webhooks support guide](https://support.phrase.com/hc/en-us/articles/5709693398812-Webhooks-TMS) for account-level event setup and retries.
* [Phrase TMS limits](https://support.phrase.com/hc/en-us/articles/5784117234972-Phrase-TMS-Limits) for current rate and account limits.

*Last updated: September 7, 2026. Track documentation changes in the [changelog](/en/changelog).*
