The String Catalog API lets custom projects run localization from your own CI pipeline. Instead of connecting a repository, your pipeline uploads the current localization files, String Catalog processes them, and your pipeline downloads a ZIP artifact with translated files to apply however your team prefers.
Use this flow when:
- Your source code stays in a private or unsupported system.
- Your release process already has a custom CI/CD pipeline.
- You want String Catalog to localize files without granting repository access.
- You need scripts to control when files are uploaded and when translated artifacts are applied.
Note
API sync jobs are available for custom projects only. Repository projects should use repository automation, pull request checks, or manual branch sync.
Quickstarts
- i18next JSON API Quickstart: upload
public/locales/<locale>/<namespace>.json, poll the sync job, download the ZIP, and apply changed files back to your repository.
Base URL
All v1 endpoints live under:
https://stringcatalog.com/api/v1
Authentication
Create a token from API Tokens in the dashboard. Tokens belong to the current team, record which team admin created them, and are scoped by both permission and project.
Send the token as a bearer token:
curl https://stringcatalog.com/api/v1/me \
-H "Authorization: Bearer $STRINGCATALOG_TOKEN" \
-H "Accept: application/json"
The API returns errors in this shape:
{
"error": {
"code": "forbidden",
"message": "This token cannot read projects.",
"request_id": "8b8c6fb6-0b33-4c4d-8a61-53c9f08fdc0a"
}
}
Validation errors include an error.errors object keyed by field. Every API response includes an X-Request-Id header, and every API error body includes the same request_id. Include that ID when contacting support.
Rate Limits
API requests are rate limited per authenticated team and at the authentication edge per IP address. Each request must satisfy both limits; whichever limit is exceeded first returns 429 Too Many Requests.
| Limit scope | Reads (GET) |
Writes (POST) |
|---|---|---|
| Authenticated team | 120 requests per minute | 30 requests per minute |
| Authentication edge IP | 120 requests per minute | 30 requests per minute |
If a request is rate limited, the API returns 429 Too Many Requests:
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Please wait before trying again.",
"request_id": "8b8c6fb6-0b33-4c4d-8a61-53c9f08fdc0a"
}
}
Rate-limited responses include a Retry-After header. CI jobs should wait that many seconds before retrying the same request.
Responses also include rate-limit budget headers:
| Header | Description |
|---|---|
X-RateLimit-Limit |
The request limit for the active read or write bucket. |
X-RateLimit-Remaining |
The number of requests left in that bucket before the window resets. |
Use these headers to slow polling or defer non-urgent writes before receiving a 429.
Permissions
Tokens can be granted these permissions:
| Permission | Allows |
|---|---|
projects:read |
List selected custom projects and setup metadata. |
sync-jobs:create |
Upload a manifest and files to queue a sync job. |
sync-jobs:read |
List sync jobs and read status, summaries, warnings, and download availability. |
sync-jobs:download |
Download the ZIP artifact for a completed sync job. |
exports:create |
Queue a read-only ZIP export of the current files. |
exports:read |
Read export status, summaries, warnings, and download availability. |
exports:download |
Download the ZIP artifact for a completed export. |
Project access is also scoped when the token is created. A token can only access the selected custom projects in its team.
Deleting a token revokes that token immediately, but it does not delete sync job or export history. A new token with the same project access can list recent sync jobs and can poll or download known sync job and export IDs while their artifacts are still retained. Idempotency-key lookup is the exception: it is scoped to the token that created the sync job, so keep the returned job ID in CI logs if you plan to rotate or revoke tokens.
Project And Language Setup
Create the custom project in the String Catalog dashboard first, then create an API token scoped to that project. The API is designed for CI upload/download automation; project creation and token creation stay in the dashboard so team admins can choose the right billing team, project access, and token permissions.
Before wiring a CI job, call GET /api/v1/projects and confirm the project setup metadata:
{
"data": [
{
"id": "prj_01hzy3n9v6q8k2m5r7t9x0c4ab",
"name": "Acme App",
"is_setup": true,
"base_locale": "en",
"target_locales": [
"es",
"fr-CA"
],
"last_synced_at": null
}
],
"meta": {
"has_more": false,
"next_cursor": null,
"limit": 20
}
}
Use base_locale to verify that String Catalog will treat the correct language as the source. Use target_locales to verify which languages are active before your CI job applies generated files.
Language discovery depends on file format:
| Format | How String Catalog discovers languages |
|---|---|
i18next_json |
Locale is inferred from the file path, such as public/locales/en/common.json or translations/common.fr-CA.json. |
android_xml |
Locale is inferred from the Android resource folder. res/values/strings.xml is the base file; res/values-es/strings.xml, res/values-pt-rBR/strings.xml, and res/values-b+es+419/strings.xml are target locales. |
xcstrings |
Languages are read from the .xcstrings catalog contents. The sourceLanguage field defines the base language. |
On a fresh custom project, upload the intended base locale first or include it first in the initial sync job. If no base language exists yet, the first parsed locale becomes the project base. You can add target languages from the dashboard, or by uploading valid localization files that contain or represent those locales.
Use sync jobs for the normal per-commit loop: upload your current files and receive changed files back. Use exports to bootstrap or recover local files from String Catalog, especially after adding languages in the dashboard.
Endpoint Overview
Use sync jobs for the normal CI loop: upload repo files, poll the job, then download only the files String Catalog changed. Use exports for bootstrap or recovery: download the full current file set that String Catalog has for a project.
| Method | Endpoint | Use |
|---|---|---|
GET |
/me |
Verify the token, team, and granted permissions. |
GET |
/projects |
List projects available to the token. |
POST |
/projects/{project}/sync-jobs |
Upload localization files and queue a sync job. |
GET |
/sync-jobs |
List recent sync jobs, optionally filtered by project, status, or idempotency key. |
GET |
/sync-jobs/{job} |
Poll sync job status and download availability. |
GET |
/sync-jobs/{job}/download |
Download a completed sync job ZIP. |
POST |
/projects/{project}/exports |
Queue a full ZIP export of the current files in String Catalog. |
GET |
/exports/{export} |
Poll export status and download availability. |
GET |
/exports/{export}/download |
Download a completed export ZIP. |
Check the Token
GET /api/v1/me
Required headers:
| Header | Description |
|---|---|
Authorization |
Bearer token. |
Example:
curl https://stringcatalog.com/api/v1/me \
-H "Authorization: Bearer $STRINGCATALOG_TOKEN" \
-H "Accept: application/json"
Response:
{
"team": {
"name": "Acme"
},
"token": {
"name": "GitHub Actions",
"abilities": [
"projects:read",
"sync-jobs:create",
"sync-jobs:read",
"sync-jobs:download",
"exports:create",
"exports:read",
"exports:download"
],
"created_by": {
"name": "Jane Example"
}
}
}
List Projects
GET /api/v1/projects
Requires projects:read.
The project list returns API-sync-eligible projects selected for the token. Results are ordered by project name and paginated by cursor.
Required headers:
| Header | Description |
|---|---|
Authorization |
Bearer token with projects:read. |
Optional query parameters:
| Query parameter | Description |
|---|---|
limit |
Number of projects to return. Defaults to 20, maximum 100. |
cursor |
Pagination cursor from meta.next_cursor. Omit this for the first page. |
Example:
curl https://stringcatalog.com/api/v1/projects \
-H "Authorization: Bearer $STRINGCATALOG_TOKEN" \
-H "Accept: application/json"
Response:
{
"data": [
{
"id": "prj_01hzy3n9v6q8k2m5r7t9x0c4ab",
"name": "Acme App",
"is_setup": true,
"base_locale": "en",
"target_locales": [
"es",
"fr-CA"
],
"last_synced_at": null
}
],
"meta": {
"has_more": false,
"next_cursor": null,
"limit": 20
}
}
Use the project id in sync-job endpoints. This is the public project ID, not the database ID. Use base_locale and target_locales as a CI preflight check before uploading files. If meta.has_more is true, request the next page with the same filters plus cursor=meta.next_cursor.
Create a Sync Job
POST /api/v1/projects/{project}/sync-jobs
Requires sync-jobs:create.
Required headers:
| Header | Description |
|---|---|
Authorization |
Bearer token with access to the project. |
Idempotency-Key |
Unique key for this CI sync attempt. Reuse the same key when retrying the same attempt. |
Send a multipart/form-data request with:
| Field | Required | Description |
|---|---|---|
manifest |
Yes | JSON string describing the files in the upload. |
files[] |
Yes | One uploaded file per manifest.files entry, in the same order. |
You can upload 1 to 100 files. Each file can be up to 20 MB, and the full upload can be up to 50 MB total.
Manifest
{
"files": [
{
"path": "Localizable.xcstrings",
"format": "xcstrings"
}
]
}
Supported formats:
| Format | Typical file |
|---|---|
xcstrings |
Localizable.xcstrings |
android_xml |
app/src/main/res/values/strings.xml |
i18next_json |
locales/en/common.json. Locale is derived from the path. Supported layouts include locales/<locale>/<namespace>.json, translations/<namespace>.<locale>.json, and <locale>.json. Files whose locale cannot be determined are treated as the project's base locale. |
Each file entry supports:
| Key | Required | Description |
|---|---|---|
path |
Yes | Safe relative destination path to use inside the upload bundle and output artifact. |
format |
Yes | xcstrings, android_xml, or i18next_json. |
Paths must be relative destinations inside the upload bundle and output artifact. Parent traversal such as ../Localizable.xcstrings is rejected.
Android XML paths must use Android resource folder conventions such as res/values/strings.xml, res/values-es/strings.xml, or res/values-b+es+419/strings.xml.
Adding Target Languages
Tip
Adding languages from the project dashboard is recommended for most teams. The dashboard handles language selection directly and is easier than shaping a file payload just to register a new locale. Use the API path when language changes need to be driven by CI or another automated workflow.
You can add a target language through the API by uploading a valid localization file that contains or represents that locale. Language registration is project-level: once a new target language is active, String Catalog may generate missing translations for that language across existing project files, not only for the file uploaded in that sync job.
Uploaded files are still file-scoped. Files omitted from a sync job are not deleted. The uploaded file is treated as the current source of truth for the file, locale, and format it represents.
i18next JSON
For i18next JSON, the locale is inferred from the file path. The file must contain at least one translation string.
{
"files": [
{ "path": "public/locales/en/common.json", "format": "i18next_json" },
{ "path": "public/locales/pt-BR/common.json", "format": "i18next_json" }
]
}
If pt-BR is not already active, uploading public/locales/pt-BR/common.json registers Portuguese (Brazil) as a target language. If the project has existing base files such as public/locales/en/onboarding.json and public/locales/en/validation.json, String Catalog may also generate missing pt-BR translations for those files.
Android XML
For Android XML, the locale is inferred from the resource folder. The XML file must contain at least one valid <string>, <plurals>, or <string-array> resource.
{
"files": [
{ "path": "app/src/main/res/values/strings.xml", "format": "android_xml" },
{ "path": "app/src/main/res/values-pt-rBR/strings.xml", "format": "android_xml" }
]
}
res/values/strings.xml is treated as the base resource file. Localized folders such as values-pt-rBR register target languages.
Xcode String Catalogs
For .xcstrings, languages are read from the catalog contents, not from the file path. To add a language, include that locale on at least one real string in the catalog. Do not add throwaway placeholder strings; use an existing key with a real translation or a value exported by Xcode or your localization tooling.
{
"sourceLanguage": "en",
"strings": {
"welcome.title": {
"localizations": {
"en": {
"stringUnit": { "state": "translated", "value": "Welcome" }
},
"pt-BR": {
"stringUnit": { "state": "translated", "value": "Bem-vindo" }
}
}
}
},
"version": "1.0"
}
For .xcstrings, the sourceLanguage field defines the base language.
Idempotency
Every sync job upload must include an Idempotency-Key header. Use a fresh key for each CI sync attempt, and reuse that same key when retrying the same attempt after a timeout or lost response.
If the same token, project, and idempotency key are submitted again, the existing sync job is returned instead of creating a duplicate.
Replayed responses include:
Idempotency-Replayed: true
New sync job responses include Idempotency-Replayed: false.
Reuse a key only for the same project, manifest, uploaded file contents, and file order. The order of manifest.files must match the order of files[] across retries. If the same token, project, and key are reused with a different payload, the API returns 409 Conflict with idempotency_key_already_used instead of silently dropping the new payload.
Example conflict response:
{
"error": {
"code": "idempotency_key_already_used",
"message": "This idempotency key was already used with a different payload.",
"request_id": "8b8c7f0c9f2a4c7a",
"lookup_url": "/api/v1/sync-jobs?project_id=prj_01hzy3n9v6q8k2m5r7t9x0c4ab&idempotency_key=i18next-a1b2c3d4"
}
}
Join lookup_url with your base URL and use the same bearer token to fetch the prior job without uploading the files again. If that token has been revoked, create a replacement token scoped to the same project and list recent sync jobs by project_id, or poll the saved job ID directly.
The key must be visible ASCII, no spaces, and 255 characters or fewer.
Idempotency records are retained with the sync job for the 7-day artifact retention window. After that window, expired jobs may be pruned and the key may be accepted again for a new sync attempt.
Example
MANIFEST='{"files":[{"path":"Localizable.xcstrings","format":"xcstrings"}]}'
curl -X POST "https://stringcatalog.com/api/v1/projects/$STRINGCATALOG_PROJECT_ID/sync-jobs" \
-H "Authorization: Bearer $STRINGCATALOG_TOKEN" \
-H "Accept: application/json" \
-H "Idempotency-Key: $CI_PIPELINE_ID-$CI_COMMIT_SHA" \
-F "manifest=$MANIFEST" \
-F "files[][email protected];type=application/json"
Android XML example:
MANIFEST='{"files":[{"path":"app/src/main/res/values/strings.xml","format":"android_xml"}]}'
curl -X POST "https://stringcatalog.com/api/v1/projects/$STRINGCATALOG_PROJECT_ID/sync-jobs" \
-H "Authorization: Bearer $STRINGCATALOG_TOKEN" \
-H "Accept: application/json" \
-H "Idempotency-Key: $CI_PIPELINE_ID-$CI_COMMIT_SHA" \
-F "manifest=$MANIFEST" \
-F "files[]=@app/src/main/res/values/strings.xml;type=application/xml"
Response:
{
"id": "job_01hzy3q4w8n6p2k5m7r9t0x1cd",
"status": "queued",
"artifact_safe_to_apply": false,
"project_id": "prj_01hzy3n9v6q8k2m5r7t9x0c4ab",
"summary": null,
"changed_files": [],
"unchanged_files": [],
"skipped_files": [],
"failed_files": [],
"warnings": [],
"download_url": null,
"error": null,
"created_at": "2026-05-11T17:00:00.000000Z",
"updated_at": "2026-05-11T17:00:00.000000Z"
}
Response fields:
| Field | Type | Description |
|---|---|---|
id |
string | Public sync job ID to use when polling or downloading. |
status |
string | Current job status. See the status table below. |
artifact_safe_to_apply |
boolean | True only when the generated artifact completed successfully and is safe for CI to apply. |
project_id |
string | Public project ID the job belongs to. |
summary |
object or null | Completion counts, including files received/changed/unchanged/skipped/failed, strings found/changed, and warning count. strings_found counts keys across the files included in this sync, not the whole project. Null while no report is available. |
changed_files |
string[] | Manifest paths whose output changed. |
unchanged_files |
string[] | Manifest paths processed without output changes. |
skipped_files |
string[] | Manifest paths skipped without failing the whole job. |
failed_files |
string[] | Manifest paths that failed during processing. |
warnings |
object[] | Non-fatal warnings. Each warning includes file when available and a human-readable message. |
download_url |
string or null | Authenticated artifact download URL when an artifact is available and not expired. This is not a presigned URL; send the same bearer token when requesting it. |
error |
object or null | Failure details when the job failed. |
created_at |
string | ISO 8601 timestamp for job creation. |
updated_at |
string | ISO 8601 timestamp for the latest job update. |
List Sync Jobs
GET /api/v1/sync-jobs
Requires sync-jobs:read.
Use this endpoint when a CI runner loses the job ID after creating a sync job, when you need to check whether a project still has active jobs, or when you need to look up the prior job behind an idempotency_key_already_used response.
Required headers:
| Header | Description |
|---|---|
Authorization |
Bearer token with sync-jobs:read. |
Optional query parameters:
| Query parameter | Description |
|---|---|
project_id |
Public project ID, such as prj_01hzy3n9v6q8k2m5r7t9x0c4ab. Recommended for CI. |
status |
One of queued, running, succeeded, failed, or expired. |
idempotency_key |
Exact idempotency key to look up for this token. This is useful after a lost response or a 409 conflict. |
limit |
Number of jobs to return. Defaults to 20, maximum 100. |
cursor |
Pagination cursor from meta.next_cursor. Omit this for the first page. |
Results are newest first and use the same sync job object shape as GET /sync-jobs/{job}. Results are limited to projects selected for the token. When idempotency_key is provided, the lookup is additionally limited to jobs created by the current token. If meta.has_more is true, request the next page with the same filters plus cursor=meta.next_cursor.
Cursors are forward-only. Reuse the same filter set when paginating; using a cursor with different filters may return unexpected results.
Examples:
curl "https://stringcatalog.com/api/v1/sync-jobs?project_id=$STRINGCATALOG_PROJECT_ID&status=running" \
-H "Authorization: Bearer $STRINGCATALOG_TOKEN" \
-H "Accept: application/json"
curl "https://stringcatalog.com/api/v1/sync-jobs?project_id=$STRINGCATALOG_PROJECT_ID&idempotency_key=$STRINGCATALOG_IDEMPOTENCY_KEY" \
-H "Authorization: Bearer $STRINGCATALOG_TOKEN" \
-H "Accept: application/json"
curl "https://stringcatalog.com/api/v1/sync-jobs?project_id=$STRINGCATALOG_PROJECT_ID&limit=20" \
-H "Authorization: Bearer $STRINGCATALOG_TOKEN" \
-H "Accept: application/json"
# -> { "data": [...], "meta": { "has_more": true, "next_cursor": "abc..." } }
curl "https://stringcatalog.com/api/v1/sync-jobs?project_id=$STRINGCATALOG_PROJECT_ID&limit=20&cursor=abc..." \
-H "Authorization: Bearer $STRINGCATALOG_TOKEN" \
-H "Accept: application/json"
# -> next page
Response:
{
"data": [
{
"id": "job_01hzy3q4w8n6p2k5m7r9t0x1cd",
"status": "running",
"artifact_safe_to_apply": false,
"project_id": "prj_01hzy3n9v6q8k2m5r7t9x0c4ab",
"summary": null,
"changed_files": [],
"unchanged_files": [],
"skipped_files": [],
"failed_files": [],
"warnings": [],
"download_url": null,
"error": null,
"created_at": "2026-05-11T17:00:00.000000Z",
"updated_at": "2026-05-11T17:00:02.000000Z"
}
],
"meta": {
"has_more": false,
"next_cursor": null,
"limit": 20
}
}
Poll a Sync Job
GET /api/v1/sync-jobs/{job}
Requires sync-jobs:read.
Required headers:
| Header | Description |
|---|---|
Authorization |
Bearer token with sync-jobs:read and access to the job's project. |
The response uses the same sync job object shape returned by sync job creation. Poll this endpoint to watch status, summary, file lists, warnings, download_url, and error update as processing progresses.
Poll every 3 to 5 seconds while the job is queued or running. For longer jobs, back off to 10 to 15 seconds between requests. If you receive 429 Too Many Requests, wait for the Retry-After header before polling again. Stop polling when the status is succeeded, failed, or expired.
Example:
curl "https://stringcatalog.com/api/v1/sync-jobs/$STRINGCATALOG_JOB_ID" \
-H "Authorization: Bearer $STRINGCATALOG_TOKEN" \
-H "Accept: application/json"
Example succeeded response:
{
"id": "job_01hzy3q4w8n6p2k5m7r9t0x1cd",
"status": "succeeded",
"artifact_safe_to_apply": true,
"project_id": "prj_01hzy3n9v6q8k2m5r7t9x0c4ab",
"summary": {
"files_received": 2,
"files_changed": 1,
"files_unchanged": 1,
"files_skipped": 0,
"files_failed": 0,
"strings_found": 42,
"strings_changed": 8,
"warnings": 1
},
"changed_files": [
"Localizable.xcstrings"
],
"unchanged_files": [
"Settings.xcstrings"
],
"skipped_files": [],
"failed_files": [],
"warnings": [
{
"file": "Localizable.xcstrings",
"message": "Locale sr-Latn is not currently supported and was skipped."
}
],
"download_url": "https://stringcatalog.com/api/v1/sync-jobs/job_01hzy3q4w8n6p2k5m7r9t0x1cd/download",
"error": null,
"created_at": "2026-05-11T17:00:00.000000Z",
"updated_at": "2026-05-11T17:04:12.000000Z"
}
Statuses:
| Status | Meaning |
|---|---|
queued |
The job is waiting to start. |
running |
String Catalog is scanning, translating, checking, or building the artifact. |
succeeded |
The artifact is ready and safe to download. |
failed |
The job could not complete. Check error, warnings, and failed_files. |
expired |
The artifact retention window has passed. |
When artifact_safe_to_apply is true and download_url is present, your pipeline can download the artifact. The URL is an authenticated API endpoint, not a presigned file URL.
Download the Artifact
GET /api/v1/sync-jobs/{job}/download
Requires sync-jobs:download.
The download_url returned by the sync job response points to this endpoint. It must be requested with an Authorization: Bearer header; unauthenticated downloads are rejected.
Required headers:
| Header | Description |
|---|---|
Authorization |
Bearer token with sync-jobs:download and access to the job's project. |
Example:
curl "$STRINGCATALOG_DOWNLOAD_URL" \
-H "Authorization: Bearer $STRINGCATALOG_TOKEN" \
-H "Accept: application/zip" \
--output stringcatalog-artifact.zip
The ZIP contains:
files/with only changed translated files, using the manifest paths.report.jsonwith summary,changed_files,unchanged_files,skipped_files, andfailed_files.warnings.jsonwith non-fatal warnings.manifest.jsonwith the accepted manifest, including each file'ssha256and bytesizeso CI can verify the uploaded inputs.
Unchanged files are not duplicated in files/. Use report.json or the sync job response to distinguish changed paths from files that were processed without output changes.
Artifacts expire after 7 days.
Create an Export
POST /api/v1/projects/{project}/exports
Requires exports:create.
Use an export when you need the full current file set from String Catalog, such as after adding target languages in the dashboard, recovering a lost local repository, or onboarding a new project checkout. Do not run exports on every CI build; exports are a bootstrap and recovery tool, not part of the normal sync loop.
Optional headers:
| Header | Description |
|---|---|
Idempotency-Key |
Optional key to dedupe retries of the same export request during the 7-day retention window. |
Example:
curl -X POST "https://stringcatalog.com/api/v1/projects/$STRINGCATALOG_PROJECT_ID/exports" \
-H "Authorization: Bearer $STRINGCATALOG_TOKEN" \
-H "Accept: application/json" \
-H "Idempotency-Key: bootstrap-$CI_COMMIT_SHA"
Response:
{
"id": "exp_01hzy4b4w8n6p2k5m7r9t0x1cd",
"status": "queued",
"project_id": "prj_01hzy3n9v6q8k2m5r7t9x0c4ab",
"summary": null,
"files_included": [],
"warnings": [],
"download_url": null,
"error": null,
"created_at": "2026-05-11T17:00:00.000000Z",
"updated_at": "2026-05-11T17:00:00.000000Z"
}
Response fields:
| Field | Type | Description |
|---|---|---|
id |
string | Public export ID to use when polling or downloading. |
status |
string | Current export status: queued, running, succeeded, failed, or expired. |
project_id |
string | Public project ID the export belongs to. |
summary |
object or null | Completion counts, including included file count, included locale count, and warning count. Null while no report is available. |
files_included |
string[] | Repository-relative paths included in the export ZIP. |
warnings |
object[] | Non-fatal warnings. Each warning includes file when available and a human-readable message. |
download_url |
string or null | Authenticated artifact download URL when the export has succeeded and the artifact is available. This is not a presigned URL; send the same bearer token when requesting it. |
error |
object or null | Failure details when the export failed. |
created_at |
string | ISO 8601 timestamp for export creation. |
updated_at |
string | ISO 8601 timestamp for the latest export update. |
New export responses include Idempotency-Replayed: false. Replayed export responses include Idempotency-Replayed: true.
Poll an Export
GET /api/v1/exports/{export}
Requires exports:read.
Poll until the export reaches succeeded, failed, or expired.
Exports omit artifact_safe_to_apply. Check status === "succeeded" and download_url !== null before downloading.
curl "https://stringcatalog.com/api/v1/exports/$STRINGCATALOG_EXPORT_ID" \
-H "Authorization: Bearer $STRINGCATALOG_TOKEN" \
-H "Accept: application/json"
Succeeded response:
{
"id": "exp_01hzy4b4w8n6p2k5m7r9t0x1cd",
"status": "succeeded",
"project_id": "prj_01hzy3n9v6q8k2m5r7t9x0c4ab",
"summary": {
"files_included": 12,
"locales_included": 4,
"warnings": 0
},
"files_included": [
"public/locales/en/common.json",
"public/locales/es/common.json"
],
"warnings": [],
"download_url": "https://stringcatalog.com/api/v1/exports/exp_01hzy4b4w8n6p2k5m7r9t0x1cd/download",
"error": null,
"created_at": "2026-05-11T17:00:00.000000Z",
"updated_at": "2026-05-11T17:00:04.000000Z"
}
Empty exports can succeed. In that case the ZIP contains an empty files/ directory and summary.files_included is 0.
Download an Export
GET /api/v1/exports/{export}/download
Requires exports:download.
The download_url is bearer-authenticated, not a presigned public URL. Include the same Authorization header when downloading.
curl "https://stringcatalog.com/api/v1/exports/$STRINGCATALOG_EXPORT_ID/download" \
-H "Authorization: Bearer $STRINGCATALOG_TOKEN" \
-H "Accept: application/zip" \
--output stringcatalog-export.zip
The ZIP uses the same repository-relative layout as sync artifacts:
| File | Description |
|---|---|
files/ |
Full current exported files. Copy this directory over your repository root to bootstrap or recover local files. |
report.json |
Export summary with mode: "export", files_included, locale count, and warnings. |
warnings.json |
Non-fatal warnings encountered while building the export. |
manifest.json |
Export manifest, including each included file's path, format, sha256, and byte size. |
CI Workflow Pattern
- Create a custom project in the dashboard.
- Create a scoped API token with the needed permissions and project access.
- Store the token and project ID in CI secrets.
- Upload the current localization files with
POST /projects/{project}/sync-jobs. - Poll
GET /sync-jobs/{job}until the status is terminal. - If
artifact_safe_to_applyis true, download and unzip the artifact. - Apply the changed files from
files/back into your working tree. - Commit, open a pull request, or publish according to your own pipeline.
Common Errors
| Code | Status | Meaning |
|---|---|---|
invalid_token |
401 | The request did not use a valid API token. |
forbidden |
403 | The token is missing permission or project scope. |
not_found |
404 | The project, job, export, or artifact was not found. |
method_not_allowed |
405 | The endpoint exists, but not for the HTTP method used. |
payload_too_large |
413 | The upload exceeded the server request-size limit before API validation could run. |
unsupported_media_type |
415 | The request used a content type the endpoint cannot process. |
unsupported_project_type |
422 | API sync jobs and exports are only available for custom projects. |
subscription_required |
402 | The project owner's team needs an active subscription. |
validation_failed |
422 | The request body, manifest, file list, or idempotency key is invalid. |
idempotency_key_already_used |
409 | The same token, project, and idempotency key were reused with a different payload. The error includes lookup_url so you can inspect the prior job without uploading again. |
artifact_not_ready |
409 | The requested sync or export artifact has not finished building yet. Poll the resource until it succeeds before downloading. |
artifact_expired |
410 | The sync or export artifact has expired. The resource status may also be expired. |