Project Duplicator API
Create a new project from an existing one: its settings, its members and its teams. No content is copied — no files, strings, translations, TM or glossary entries, whatever the flags say: they are named after settings so that none of them reads as a copy of content.
Contract version 1. Call it through the Crowdin API with a token that has the
application.proxy scope:
- Enterprise:
https://{organization}.api.crowdin.com/api/v2/applications/create-project-app/api/{path} - crowdin.com:
https://api.crowdin.com/api/v2/applications/create-project-app/api/{path}
Parameters go in the JSON body or in the query; Crowdin merges the query into the body, so numbers and booleans are
accepted as text too. Crowdin wraps every answer into data and keeps its HTTP status, so every body says
ok and status itself.
How a copy goes
GET /duplication-previewshows what would be copied, who would be added, invited or skipped and why, and what is never copied. It writes nothing, and it is also the export of who has access to the source.POST /duplicationwith anidempotencyKeyanswers once the project is created, with the status of the copy. Members, teams and resources are copied after the answer.GET /duplication-statuseverypollAfterMs(5000 ms), or withwait, until the status is one ofsucceeded,partial,failed,needsAttention.
Statuses: queued, running, succeeded, partial, failed, needsAttention, interrupted. interrupted is not final: the app is restarting, and
the copy goes on once it is back, so keep polling. partial means the project exists but something did not
make it: show it as created with remarks, never as a success. needsAttention means Crowdin did not confirm
the creation and the app will not guess: candidates lists the projects it may have made, and a POST with the
same key and adoptProjectId goes on with one of them. Phases, in progress.phase only:
creating, settings, members, teams, resources, done.
Endpoints
GET /duplication-preview
Show what would be copied from a source project, which members would be added with their roles, who would be skipped, and what this app never copies. No content is copied — no files, strings, translations, TM or glossary entries. On crowdin.com adding a translator, or any other member, sends them an invitation email, so the preview counts the emails first.
Path through Crowdin: GET …/api/duplication-preview
| Parameter | Type | Required | Platform | Description |
|---|---|---|---|---|
sourceProjectId | integer | yes | both | The project to copy. |
targetGroupId | integer | no | Enterprise | The group the copy would be created in; 0 or none for the root. |
workflowTemplateId | integer | no | Enterprise | The workflow template of the copy. Without it the preview maps the steps onto the template whose steps match the source and names it in target.suggestedWorkflowTemplateId. |
copy | object | no | both | The copy flags below, each a boolean. A flag left out takes its default; teams follows members unless it is given. Any other key is refused. |
memberOptions | object | no | both | { onUnmappedSteps: "skipRole" | "strict" | "allContent" (default "skipRole"), acknowledgeInvitations: boolean }. See "Members". |
Response 200
{
"ok": true,
"status": "ok",
"platform": "enterprise",
"source": {
"id": 123,
"name": "Mobile App",
"identifier": "mobile-app",
"type": "file-based",
"sourceLanguageId": "en",
"sourceLanguageName": "English",
"targetLanguages": [
{
"id": "uk",
"name": "Ukrainian"
}
],
"hasAdvancedWorkflow": true,
"workflowSteps": [
{
"id": 313,
"type": "Translate",
"title": "Translation"
}
],
"settingsValues": {
"qaCheckIsActive": true
}
},
"target": {
"groupId": 45,
"groupPath": "Workspace/Mobile",
"workflowTemplateId": 7,
"suggestedWorkflowTemplateId": null,
"workflowTemplateRequired": true,
"stepMapping": [
{
"sourceStepId": 313,
"targetStepId": 998,
"matchedBy": "positional"
}
],
"mappingMethod": "positional"
},
"settings": {
"groups": [
{
"key": "qa",
"title": "Quality assurance",
"default": true,
"resource": false,
"fields": [
"qaCheckIsActive",
"qaCheckCategories"
],
"values": {
"qaCheckIsActive": true
},
"requires": [],
"blockedBy": null
},
{
"key": "placeholders",
"title": "Placeholders",
"default": true,
"resource": true,
"fields": [],
"values": {
"switchedOff": [
{
"id": "printfSpecifier",
"label": "C-style format specifiers"
}
],
"customPlaceholders": [
{
"name": "Ticket reference",
"customPlaceholderId": 12,
"type": "high",
"index": 0,
"isBlocking": true,
"formats": [
"docx"
]
}
]
},
"requires": [],
"blockedBy": null
}
]
},
"members": {
"copied": true,
"total": 42,
"byOutcome": {
"willAdd": 33,
"willInvite": 0,
"willSkip": 9
},
"estimatedInvitations": 0,
"list": [
{
"id": 11,
"login": "ann",
"name": "Ann",
"roles": [
"translator"
],
"languages": [
"uk"
],
"allLanguages": false
}
],
"skipped": [
{
"id": 12,
"login": "eve",
"name": "Eve",
"code": "MEMBER_BLOCKED_SKIPPED",
"message": "This member is blocked in the source project, so they were not added."
}
],
"detailsTruncated": false
},
"teams": {
"copied": true,
"supported": true,
"total": 3,
"willAdd": 3,
"list": [
{
"id": 4,
"name": "Reviewers"
}
],
"skipped": []
},
"resources": {
"labels": 12,
"exporterSettings": 3,
"taskSettingsTemplates": 1,
"reportSettingsTemplates": null,
"resourceBindings": 2,
"placeholders": 2
},
"warnings": [],
"notCopied": [
{
"key": "content.files",
"title": "Files and folders",
"reason": "outOfScope",
"code": null
}
],
"unknownFields": [],
"limits": {
"maxMembers": 5000,
"maxTeams": 500,
"memberBatchSize": 25,
"maxWaitSeconds": 60,
"invitationConfirmationThreshold": 50
},
"contract": {
"version": 1,
"duplicationUrl": "/duplication",
"statusUrl": "/duplication-status",
"idempotencyKeyRequired": true,
"pollAfterMs": 5000
}
}POST /duplication
Create a new project from an existing one: settings, members with their roles and, on Enterprise, teams. No content is copied — no files, strings, translations, TM or glossary entries. On crowdin.com adding a translator, or any other member, sends them an invitation email.
Path through Crowdin: POST …/api/duplication
| Parameter | Type | Required | Platform | Description |
|---|---|---|---|---|
idempotencyKey | string | yes | both | Up to 128 characters, one per copy. The same key with the same request answers with the same duplication and never creates a second project; the same key with another request is IDEMPOTENCY_CONFLICT. A copy that failed with UPSTREAM_ERROR or UPSTREAM_RATE_LIMITED before its project existed is tried again by the same request with the same key, which first looks for a project Crowdin may have created after all; any other failure answers the same again. |
sourceProjectId | integer | yes | both | The project to copy. |
name | string | yes | both | The name of the new project. |
identifier | string | no | crowdin.com | The identifier of the new project: letters, digits, "-" and "_", at least 3. Without it the app derives one from the name and adds -2, -3 when it is taken; one given here is never replaced, and a taken one is IDENTIFIER_TAKEN. On Enterprise it is ignored with FIELD_IGNORED_ON_PLATFORM. |
targetGroupId | integer | no | Enterprise | The group to create the copy in; 0 or none for the root. |
workflowTemplateId | integer | no | Enterprise | The workflow template of the copy. Required when the preview says target.workflowTemplateRequired: the source has a workflow of its own, and Crowdin does not remember which template it was made from. A workflow Crowdin does not answer with is UPSTREAM_ERROR rather than a guess, on the preview as on the POST. |
adoptProjectId | integer | no | both | Only to resolve a duplication in needsAttention, sent again with its idempotencyKey: the candidate project the copy goes on with, as the duplication planned it, whatever else the request says. Anything but one of its candidates is ADOPT_CANDIDATE_MISMATCH; the candidate it already went on with is answered as a replay. |
copy | object | no | both | The copy flags below, each a boolean. A flag left out takes its default; teams follows members unless it is given. Any other key is refused. |
memberOptions | object | no | both | { onUnmappedSteps: "skipRole" | "strict" | "allContent" (default "skipRole"), acknowledgeInvitations: boolean }. See "Members". |
wait | number | no | both | 0..60 seconds to wait for the copy to finish before answering. A long poll instead of a frequent one; an interrupted copy is answered at once. |
Request
{
"idempotencyKey": "7f1c2a0e-3c1b-4d0e-9a57-5a4c0f1d2e3b",
"sourceProjectId": 123,
"name": "Mobile App (copy)",
"targetGroupId": 45,
"workflowTemplateId": 7,
"copy": {
"targetLanguages": true,
"qa": true,
"placeholders": true,
"sourceStringSettings": true,
"importSettings": true,
"exportSettings": true,
"translationSettings": true,
"tmSettings": true,
"aiPrompts": true,
"notifications": true,
"languageMapping": true,
"inContext": false,
"labels": false,
"exporterSettings": false,
"taskSettingsTemplates": false,
"reportSettingsTemplates": false,
"resourceBindings": false,
"members": true,
"teams": true
},
"memberOptions": {
"onUnmappedSteps": "skipRole",
"acknowledgeInvitations": false
},
"wait": 0
}Response 200
{
"ok": true,
"status": "running",
"duplicationId": "dup_01J8ZB6Q4W2N5V7X9Y0A1B2C3D",
"idempotencyKey": "7f1c2a0e-3c1b-4d0e-9a57-5a4c0f1d2e3b",
"project": {
"id": 987,
"name": "Mobile App (copy)",
"identifier": null,
"url": "https://acme.crowdin.com/u/projects/987/files"
},
"candidates": [],
"startedAt": "2026-09-23T10:00:00.000Z",
"finishedAt": null,
"progress": {
"phase": "members",
"done": 12,
"total": 45
},
"report": {
"settings": {
"requested": [
"targetLanguages",
"qa",
"aiPrompts"
],
"copied": [
"targetLanguages",
"qa"
],
"failed": [
{
"key": "aiPrompts",
"code": "SETTING_REJECTED",
"message": "qaCheckActionAiPromptId: <the wording of Crowdin>",
"details": {
"fields": [
"qaCheckActionAiPromptId"
],
"retryable": true
}
}
]
},
"members": {
"total": 42,
"counts": {
"added": 33,
"updated": 0,
"invited": 0,
"skipped": 8,
"failed": 1
},
"added": [
{
"id": 11,
"login": "ann",
"roles": [
"translator"
],
"languages": [
"uk"
],
"allLanguages": false
}
],
"skipped": [],
"failed": [],
"detailsTruncated": false
},
"teams": {
"supported": true,
"total": 3,
"counts": {
"added": 2,
"skipped": 1,
"failed": 0
},
"added": [
{
"id": 4,
"name": "Reviewers"
}
],
"skipped": [
{
"id": 9,
"name": "FR squad",
"code": "TEAM_PERMISSIONS_NOT_PORTABLE",
"message": "The access of this team cannot be rebuilt in the new project, so the team was not added."
}
],
"failed": []
},
"resources": {
"labels": {
"total": 12,
"copied": 12,
"failed": 0,
"skipped": 0,
"items": []
},
"placeholders": {
"total": 2,
"copied": 1,
"failed": 0,
"skipped": 1,
"items": [
{
"name": "Ticket reference",
"outcome": "skipped",
"code": "CUSTOM_PLACEHOLDER_FORMAT_UNAVAILABLE",
"message": "This custom placeholder is limited to file formats the new project cannot use, such as one of an app the project has no access to, so it was not assigned. Give the project access to that format, then assign the placeholder by hand."
}
]
}
},
"notCopied": [
{
"key": "content.files",
"title": "Files and folders",
"reason": "outOfScope",
"code": null
},
{
"key": "webhooks",
"title": "Webhooks",
"reason": "outOfScope",
"code": "WEBHOOKS_NOT_COPIED"
}
],
"warnings": [],
"unknownFields": []
},
"errors": [],
"statusUrl": "/duplication-status",
"pollAfterMs": 5000,
"replayed": false
}GET /duplication-status
Report of a duplication: which settings were copied, which members were added with their roles or, on crowdin.com, invited by email, who was skipped, and who failed with the reason. No content is copied.
Path through Crowdin: GET …/api/duplication-status
| Parameter | Type | Required | Platform | Description |
|---|---|---|---|---|
duplicationId | string | no | both | The duplicationId the POST answered with. |
idempotencyKey | string | no | both | Or the key the POST was sent with. One of the two is required. |
wait | number | no | both | 0..60 seconds to wait for the copy to finish before answering. A long poll instead of a frequent one; an interrupted copy is answered at once. |
Response 200
{
"ok": true,
"status": "partial",
"duplicationId": "dup_01J8ZB6Q4W2N5V7X9Y0A1B2C3D",
"idempotencyKey": "7f1c2a0e-3c1b-4d0e-9a57-5a4c0f1d2e3b",
"project": {
"id": 987,
"name": "Mobile App (copy)",
"identifier": null,
"url": "https://acme.crowdin.com/u/projects/987/files"
},
"candidates": [],
"startedAt": "2026-09-23T10:00:00.000Z",
"finishedAt": "2026-09-23T10:01:12.000Z",
"progress": {
"phase": "done",
"done": 45,
"total": 45
},
"report": {
"settings": {
"requested": [
"targetLanguages",
"qa",
"aiPrompts"
],
"copied": [
"targetLanguages",
"qa"
],
"failed": [
{
"key": "aiPrompts",
"code": "SETTING_REJECTED",
"message": "qaCheckActionAiPromptId: <the wording of Crowdin>",
"details": {
"fields": [
"qaCheckActionAiPromptId"
],
"retryable": true
}
}
]
},
"members": {
"total": 42,
"counts": {
"added": 33,
"updated": 0,
"invited": 0,
"skipped": 8,
"failed": 1
},
"added": [
{
"id": 11,
"login": "ann",
"roles": [
"translator"
],
"languages": [
"uk"
],
"allLanguages": false
}
],
"skipped": [],
"failed": [],
"detailsTruncated": false
},
"teams": {
"supported": true,
"total": 3,
"counts": {
"added": 2,
"skipped": 1,
"failed": 0
},
"added": [
{
"id": 4,
"name": "Reviewers"
}
],
"skipped": [
{
"id": 9,
"name": "FR squad",
"code": "TEAM_PERMISSIONS_NOT_PORTABLE",
"message": "The access of this team cannot be rebuilt in the new project, so the team was not added."
}
],
"failed": []
},
"resources": {
"labels": {
"total": 12,
"copied": 12,
"failed": 0,
"skipped": 0,
"items": []
},
"placeholders": {
"total": 2,
"copied": 1,
"failed": 0,
"skipped": 1,
"items": [
{
"name": "Ticket reference",
"outcome": "skipped",
"code": "CUSTOM_PLACEHOLDER_FORMAT_UNAVAILABLE",
"message": "This custom placeholder is limited to file formats the new project cannot use, such as one of an app the project has no access to, so it was not assigned. Give the project access to that format, then assign the placeholder by hand."
}
]
}
},
"notCopied": [
{
"key": "content.files",
"title": "Files and folders",
"reason": "outOfScope",
"code": null
},
{
"key": "webhooks",
"title": "Webhooks",
"reason": "outOfScope",
"code": "WEBHOOKS_NOT_COPIED"
}
],
"warnings": [],
"unknownFields": []
},
"errors": [],
"statusUrl": "/duplication-status",
"pollAfterMs": 5000
}Errors
The same envelope on all three; error.httpStatus is the HTTP status of the answer, and
error.duplicationId names the duplication a refused copy ended in.
{
"ok": false,
"status": "error",
"error": {
"code": "SOURCE_FORBIDDEN",
"message": "You need manager access to the source project to copy its settings and members.",
"httpStatus": 403,
"details": {
"projectId": 123
},
"duplicationId": null
}
}
What is copied
Always, whatever the flags: sourceLanguageId, type, description, visibility (crowdin.com), fields (Enterprise). The flags:
| Flag | Default | What it copies | Requires |
|---|---|---|---|
targetLanguagesTarget languages | true | targetLanguageIds | — |
qaQuality assurance | true | qaCheckIsActive, qaCheckCategories, qaChecksIgnorableCategories, qaApprovalsCount (Enterprise), customQaCheckIds (Enterprise), externalQaCheckIds (Enterprise), externalQaChecksIgnorable (Enterprise) | — |
placeholdersPlaceholders | true | Which built-in placeholders are recognized and, on Enterprise, the custom ones assigned to the project. | — |
sourceStringSettingsSource string settings | true | normalizePlaceholder, saveMetaInfoInSource (file-based only) | — |
importSettingsImport | true | normalizePlaceholder, tagsDetection | — |
exportSettingsExport | true | skipUntranslatedStrings, skipUntranslatedFiles (file-based only), autoTranslateDialects, exportApprovedOnly (crowdin.com), exportWithMinApprovalsCount (Enterprise), exportStringsThatPassedWorkflow (Enterprise) | — |
translationSettingsTranslation settings | true | languageAccessPolicy (crowdin.com), delayedWorkflowStart (Enterprise), translateDuplicates (file-based only), isMtAllowed, publicDownloads, glossaryAccess, glossaryAccessOption, taskBasedAccessControl, taskReviewerIds (Enterprise), hiddenStringsProofreadersAccess, mtPreTranslate (crowdin.com) | — |
tmSettingsTranslation Memory | true | autoSubstitution, tmContextType (file-based only), showTmSuggestionsDialects, tmApprovedSuggestionsOnly, useGlobalTm (crowdin.com), tmPreTranslate (crowdin.com), tmPenalties | — |
aiPromptsAI Default Prompts | true | qaCheckActionAiPromptId, editorSuggestionAiPromptId, preTranslationAiPromptId, contextReviewAiPromptId, alignmentActionAiPromptId (Enterprise), aiPreTranslate (crowdin.com) | — |
notificationsNotifications | true | notificationSettings | — |
languageMappingLanguage Mapping | true | languageMapping | — |
inContextIn-Context | false | inContext, inContextProcessHiddenStrings, inContextPseudoLanguageId | — |
labelsLabels | false | The project's own labels, without the strings they are assigned to. | — |
exporterSettingsFile format and strings exporter settings | false | Parser options per file format with their custom segmentation rules, and the XLIFF, Android and iOS export options. | — |
taskSettingsTemplatesTask settings templates | false | Saved templates, not tasks; they need Target languages, and only assignees who joined the copy can stay. | — |
reportSettingsTemplatesReport settings templates | false | The project's own saved templates, not the account-wide ones every project already sees. Only on crowdin.com. | — |
resourceBindingsTranslation memories, glossaries, style guides and the savings report template | false | assignedStyleGuides, assignedTms, assignedGlossaries, savingsReportSettingsTemplateId | — |
membersMembers | true | The members of the project with their roles, languages and workflow steps. | targetLanguages |
teamsTeams | as members | The teams of the project with their roles. Only on Enterprise. | targetLanguages |
A setting Crowdin refuses on creation is left out and the project is created without it, reported in
report.settings.failed with the wording of Crowdin and a partial status. Resource bindings attach
the TMs, glossaries and style guides of the source, its default TM and glossary among them, to the copy as additional
ones; the copy keeps the TM and the glossary Crowdin creates with it as its defaults, so its translations never go into a
TM of the source. Their entries are not copied. TM priorities stay those of the source on crowdin.com, where the highest
one is the number of TMs of the project owner. Enterprise accepts no priority above the number of TMs of the project, so
there priorities the copy cannot have become their rank, in the same order. The preview counts what each
resource flag would copy under resources, one key per flag; a count is null when the app does
not copy that group here (settings.groups[].blockedBy says why) or could not count it.
placeholders, on by default, copies the placeholders the project recognizes, which Crowdin keeps apart
from the settings of the project: every built-in placeholder switched off in the source is switched off in the copy, one
JSON-Patch each, and on Enterprise the custom placeholders of the organization assigned to the source are assigned to the
copy with the same type, index, isBlocking and formats, in the order
of their index. The custom placeholders themselves belong to the organization and are shared, so none is created or
changed. One limited to formats the copy cannot use is skipped with CUSTOM_PLACEHOLDER_FORMAT_UNAVAILABLE,
and one deleted meanwhile with CUSTOM_PLACEHOLDER_DELETED; neither makes the copy partial.
Placeholder unification, normalizePlaceholder, is a setting of sourceStringSettings and
importSettings. In the preview, settings.groups[] of placeholders lists the built-in
placeholders switched off in the source under values.switchedOff and, on Enterprise, its custom placeholders
under values.customPlaceholders, null where Crowdin did not list them. Where Crowdin has no
placeholder routes (Crowdin Enterprise of the Connector edition answers 404), the group is reported in
notCopied with endpointUnavailable, which is also its blockedBy in the preview,
it is not counted, and the copy is not partial for it. A caller that leaves the flag out gets placeholders copied.
Resources are copied last, after the teams and the members. Labels go by title, without the strings they are assigned
to; system labels are left alone. Task settings templates go with their languages as they are and keep as assignees
only the people and teams the copy has; a template without languages, and every template when the target languages are
not copied, is skipped with LANGUAGE_NOT_IN_TARGET. The report counts every resource flag under
report.resources (total, copied, failed, skipped), and
items names what failed or left something behind. A resource list Crowdin refuses to give makes the copy
partial, never stops it.
Members
Members are added with the roles they have in the source, rebuilt for the languages and workflow steps of the copy.
memberOptions.onUnmappedSteps decides what happens to access to a step the copy has no match for:
skipRole (default) leaves that access out, strict refuses the copy before anything is created,
allContent widens it to all steps and names every widened grant in the report. The owner of the source,
blocked members, pending invitations (crowdin.com), organization admins and group managers without a project role of
their own, and technical accounts are never added, each with its code; an admin or a group manager with a role of their
own gets that role alone. On Enterprise a person who has not accepted the invitation to the organization yet is copied
like any member. On Enterprise the teams of the source are added after the members, since Crowdin skips whoever a team
already makes a manager of the copy with their roles of their own, and the people of a team are not added one by
one.
Access can be limited to Translate and Proofread steps only, so only those steps of the source
and of the workflow template are compared; pre-translation, vendor, application and custom code steps neither count nor
shift the positions. They map by position when both have them in the same order of types, by position within a type when
only the order or the titles differ, and not at all otherwise: target.mappingMethod is
positional, byType or none, and target.stepMapping pairs each such
step of the source with its step in the copy, null when it has none. A member or a team left without a role
because its languages are not target languages of the copy is skipped with LANGUAGE_NOT_IN_TARGET under
every policy. When the preview counted on those languages and the creation of the copy lost them, the member or the team
fails with the same code instead.
With copy.members or copy.teams off, the preview keeps members and
teams in their shape with copied: false and total: null: nobody was read, so
there is nothing to count. teams.supported is then null on Enterprise, since whether Crowdin
lists the teams of the source is only known once they are asked for.
On Enterprise the task reviewers of translationSettings are set once the members and the teams are in, and only those
who are managers of the copy then: Crowdin accepts no one else. The others are listed in notCopied under
taskReviewerIds with details.reason notManagers and their
details.userIds; a refusal of Crowdin is a failed setting of a partial copy.
Never copied
| What | Why |
|---|---|
content.files | Files and folders |
content.branches | Branches |
content.strings | Source strings |
content.translations | Translations and approvals |
content.translationMemory | Translation memory entries |
content.glossary | Glossary terms |
content.screenshots | Screenshots |
content.comments | Comments and issues |
content.tasks | Tasks |
content.distributions | Distributions and bundles |
content.labelAssignments | Labels assigned to strings |
webhooks | Webhooks are not copied: Crowdin does not give back their authorization headers, and a copy would call the same integrations a second time. Set them up in the new project by hand. |
id | Assigned by Crowdin to the new project. |
identifier | crowdin.com derives it from the new name and needs it unique; Enterprise does not accept it on create. |
name | The name of the copy comes from the request. |
userId | The owner of the copy is whoever runs the duplication. |
createdAt | Set by Crowdin. |
updatedAt | Set by Crowdin. |
lastActivity | Set by Crowdin. |
logo | Project creation does not accept a logo. |
webUrl | Belongs to the source project. |
isSuspended | State of the organization, not a project setting. |
exportTranslatedOnly | Deprecated duplicate of skipUntranslatedStrings, which is copied. |
sourceLanguage | Expanded object; sourceLanguageId is copied. |
targetLanguages | Expanded objects; targetLanguageIds is copied. |
inContextPseudoLanguage | Expanded object; inContextPseudoLanguageId is copied. |
defaultTmId | The copy keeps the TM Crowdin creates with it as its default, so its translations never go into a TM of the source; resourceBindings attaches the default TM of the source as an additional one. |
defaultGlossaryId | The copy keeps the glossary Crowdin creates with it as its default; resourceBindings attaches the default glossary of the source as an additional one. |
translateDuplicates | Fixed by Crowdin for string-based projects and not accepted on their creation. |
cname | A unique domain: a copy would either take it from the source or fail on the conflict. |
groupId | The target group comes from the request. |
background | Deprecated and not accepted on create. |
publicUrl | Belongs to the source project. |
workflowId | The source project's own workflow, not a template: the workflow of the copy comes from the chosen template. |
hasCrowdsourcing | Derived by Crowdin from the workflow. |
isExternal | Vendor relationship of the source project. |
externalType | Vendor relationship of the source project. |
externalProjectId | Vendor relationship of the source project. |
externalOrganizationId | Vendor relationship of the source project. |
clientOrganizationId | Vendor relationship of the source project. |
notCopied[].reason is one of outOfScope, notImplemented, platformUnsupported, crowdinOnly, endpointUnavailable.
Codes
HTTP errors
| Code | HTTP | Message |
|---|---|---|
VALIDATION_FAILED | 400 | A parameter of the request is missing or not valid: error.details.field names it. |
SETTING_REJECTED | 400 | Crowdin rejected some of the copied settings. |
INVALID_PROJECT_ID | 400 | The source project id must be a positive integer. |
IDEMPOTENCY_KEY_REQUIRED | 400 | Every duplication request needs an idempotency key of at most 128 characters, so that a retry does not make a second copy. |
TARGET_LANGUAGES_REQUIRED_FOR_MEMBERS | 400 | Members and teams can only be copied together with the target languages. Turn on the target languages or turn off the members and teams. |
IDENTIFIER_TAKEN | 409 | Another project already uses this identifier. Choose a different one. |
IDEMPOTENCY_CONFLICT | 409 | This idempotency key was already used for a different duplication request. Use a new key for a new request. |
ADOPT_CANDIDATE_MISMATCH | 400 | The project to adopt is not one of the candidates found for this duplication. Choose one of its candidates. |
SOURCE_TOO_LARGE | 400 | The source project has more members or teams than this app copies at once. |
WORKFLOW_TEMPLATE_REQUIRED | 400 | The source project has its own workflow, so the copy needs a workflow template. Choose one and start again. |
WORKFLOW_STEPS_NOT_MAPPABLE | 400 | Some workflow steps members of the source project have access to have no match in the chosen template, so nothing was created. |
INVITATIONS_NOT_ACKNOWLEDGED | 400 | Copying these members sends many invitation emails on crowdin.com. Confirm the invitations and start again. |
INVALID_TOKEN | 401 | The request is not signed with a valid Crowdin token. Call this API through the Crowdin API of your organization. |
MISSING_TOKEN | 401 | The request carries no Crowdin token. Call this API through the Crowdin API of your organization. |
TOKEN_EXPIRED | 401 | The Crowdin token of the request has expired. Send the request again. |
SOURCE_FORBIDDEN | 403 | You need manager access to the source project to copy its settings and members. |
TARGET_GROUP_NOT_FOUND | 404 | The target group does not exist in this organization. |
APP_NOT_INSTALLED | 404 | The app is installed, but the organization is unknown to it. Reinstall the app. |
SOURCE_NOT_FOUND | 404 | The source project does not exist, or the app cannot see it. |
DUPLICATION_NOT_FOUND | 404 | There is no such duplication. |
UPSTREAM_RATE_LIMITED | 429 | Crowdin is limiting how often this app may call it right now. Try again in a few minutes. |
INTERNAL_ERROR | 500 | Something went wrong in the app. Try again later, with a new idempotencyKey for a copy that ended with this error. |
UPSTREAM_ERROR | 502 | Crowdin did not answer as expected. Try again in a few minutes. |
APP_RESTARTING | 503 | The app is restarting. Send the same request with the same idempotencyKey again in a minute: it never makes a second copy. |
Two of them came after version 1 of the contract froze its catalog:
VALIDATION_FAILED400: A parameter the app cannot read: an empty name, an idempotencyKey over 128 characters, a malformed identifier, an unknown key in copy or memberOptions, a value of the wrong type, a wait outside its range, a status request with neither duplicationId nor idempotencyKey, or a body that is not JSON. error.details.field names the parameter.APP_RESTARTING503: A POST /duplication that comes while the app is shutting down for a restart, before the copy has started. Nothing was created: send the same request with the same idempotencyKey again in a minute.
In the report
Per setting, member, team and warning, inside a copy that as a whole went through.
| Code | Meaning |
|---|---|
SETTING_REJECTED | Crowdin rejected this setting, so the new project was created without it. |
FIELD_IGNORED_ON_PLATFORM | This value is not accepted on this platform, so it was not copied. |
WORKFLOW_STEP_CONFIG_SKIPPED | The vendor or MT engine of these workflow steps was not copied: the new project takes its workflow from the chosen template. |
ROLE_NOT_COPYABLE | This role cannot be given in the new project, so the member was not added. |
MEMBER_ALREADY_PRESENT | Already in the new project. |
MEMBER_BLOCKED_SKIPPED | This member is blocked in the source project, so they were not added. |
MEMBER_PENDING_SKIPPED | This person has not accepted the invitation to the source project yet. Pending invitations are not copied, and no second email is sent. |
INHERITED_ACCESS_NOT_PORTABLE | This member reaches the source project through the organization, a group or a team, not through a project role of their own. Adding them in person would grant access they never had, so they were not added. |
TEAM_ONLY_ACCESS | This member reaches the source project only through a copied team, so the team gives them access to the new project. |
TECHNICAL_DETECTION_UNAVAILABLE | Crowdin does not mark technical accounts among the members of a project: one is found when Crowdin refuses it. |
LANGUAGE_NOT_IN_TARGET | This language is not a target language of the new project, so access to it was not copied. |
WORKFLOW_STEP_UNMAPPED | Some workflow steps of the source project have no match in the new project. The details say whose access was left out or widened. |
STEP_RESTRICTIONS_NOT_PORTABLE | The new project has the default workflow, so these members get access to all workflow steps of their languages. |
TEAM_PERMISSIONS_NOT_PORTABLE | The access of this team cannot be rebuilt in the new project, so the team was not added. |
INVITE_LIMIT_REACHED | Crowdin reached the daily limit of invitation emails of the organization, so this member was not invited. |
MANAGERS_LIMIT_EXCEEDED | The subscription plan of the organization allows no more managers, so this member was not added. |
UPSTREAM_RESULT_UNKNOWN | Crowdin did not answer whether this member or team was added, and the new project did not show it when the app checked. Check its members before you add them by hand: Crowdin may still add them, and on crowdin.com the invitation may have been sent. |
CUSTOM_PLACEHOLDER_FORMAT_UNAVAILABLE | This custom placeholder is limited to file formats the new project cannot use, such as one of an app the project has no access to, so it was not assigned. Give the project access to that format, then assign the placeholder by hand. |
CUSTOM_PLACEHOLDER_DELETED | This custom placeholder was deleted from the organization while the copy was made, so there was nothing to assign. |
WEBHOOKS_NOT_COPIED | Webhooks are not copied: Crowdin does not give back their authorization headers, and a copy would call the same integrations a second time. Set them up in the new project by hand. |
NEEDS_ATTENTION | The duplication stopped at a point where the app cannot decide on its own. Check the details to resolve it. |
Three of them came after version 1 of the contract froze its catalog:
UPSTREAM_RESULT_UNKNOWN: A failed member or team whose write Crowdin answered with a timeout or a 5xx, when the members or the teams of the copy, read back right after, do not list it or cannot be read, or whose write a stopped run left pending when the copy cannot be read. A timeout or a 502 of the proxy does not stop the write in Crowdin, so it may still go through. The app does not send it again; on crowdin.com the invitation may have gone out, so a member other than a manager or a developer, who join at once, has invited "unknown". One the copy lists is reported as added or invited.CUSTOM_PLACEHOLDER_FORMAT_UNAVAILABLE: A custom placeholder of the source limited to file formats Crowdin does not let the copy use, such as the format of a custom file format app available to selected projects only. It is skipped rather than assigned to every format, and the copy is not partial for it.CUSTOM_PLACEHOLDER_DELETED: A custom placeholder deleted from the organization between the preview of the copy and its assignment: skipped.
Errors that never reach the app
| Answer of Crowdin | Meaning |
|---|---|
| 401 api.notAllowedScope | The token of the caller lacks the application.proxy scope, which came with API version 2025-05-13: older personal access tokens and OAuth apps do not have it. It is not INVALID_TOKEN of the app. |
| 404 on all three paths | The app is not installed, or an admin has not applied the update that added these endpoints. |
| 404 on crowdin.com | For everyone but the account the app is installed under, for good. |
| 405 | The path is right, the method is not. |
| 500 without a body | The 15-minute timeout of the proxy. A retry with the same idempotencyKey is safe. |
Limits
- At most 5000 members and 500 teams in the source (SOURCE_TOO_LARGE), limits of the app itself.
- Members are added 25 per request to Crowdin, the ones with the manager, developer or language coordinator role one per request: Crowdin checks the managers limit of the plan for a whole request, so one person who needs a new seat would get everyone in it refused.
waitup to 60 seconds.
Limitations
- Who can call it: on Enterprise the owner and the admins of the organization, on crowdin.com only the account the app is installed under. Project managers duplicate projects in the window of the app. A 403 or a 404 from Crowdin for anyone else means exactly this.
- Crowdin does not check the permission of an api module on its proxy, only whether the caller has full access to the apps of the organization: whoever can call one of the three endpoints can call all of them.
- The app acts with the permissions of whoever installed it, never with those of the caller: there is no token per user. A preview and a duplication check the caller in the app as well, for a call that comes past the proxy: on Enterprise an admin of the organization, on crowdin.com a manager of the source project. The status of a run is found by its id, 80 random bits. Enterprise has an app authentication of the type crowdin_app_with_code, which acts as the caller, and crowdin.com has none; this app does not use it and acts with the token of the installer on both.
- Crowdin waits 15 minutes for an answer and has no callbacks, so the API is asynchronous: a POST answers once the project is created, and wait is capped at 60 seconds.
- The paths of api modules have no dynamic segments: every id is a parameter.
- On crowdin.com a copy always gets the default workflow, as project creation there accepts no workflow, so access the source limits to some workflow steps is lost in the copy: those members get all the steps of their languages. This cannot be detected through the API: for a source with a workflow of its own Crowdin already reports such access as all the steps of the language, so the preview and the report do not name these members. STEP_RESTRICTIONS_NOT_PORTABLE appears only for the access Crowdin does report as limited to some steps.
- On crowdin.com every member added gets an email against the daily limit of invitations of the organization: 1000 a day outside a trial, 2 to 100 a day in a trial depending on its age, and a separate limit of 50 for invitations with the manager role (numbers of Crowdin that may change). A manager or a developer joins the copy at once; a translator, a proofreader or a language coordinator is invited and joins once they accept. The rest of the limit cannot be read; above 50 estimated invitations the POST needs memberOptions.acknowledgeInvitations, and once Crowdin refuses one the remaining ones are reported as INVITE_LIMIT_REACHED.
- On crowdin.com a member is a manager, a developer or holds language roles, never two of these: a member the source lists as a manager or a developer with language roles as well is added with the manager or the developer role alone. A manager has every language with the role; for a developer the preview and the report name the language roles left out in notCopyableRoles.
- Rolling the app back removes its api modules from every organization by itself; bringing them back takes another Apply of the update by an admin.
- On Enterprise the people who reach the source through a team are read from the members of its teams, with the team:read scope of the same update. Where it cannot be read no team is copied, and the teams are reported as TEAM_PERMISSIONS_NOT_PORTABLE.
- Crowdin marks no technical account in the members of a project: one is skipped when Crowdin refuses to add it, with TECHNICAL_DETECTION_UNAVAILABLE.
- On Enterprise the custom placeholders of the copy are named in the preview and the report by their description, or their definition, from the list of the organization, which the custom-placeholder:read scope of the same update reads. Until an admin applies it, or where Crowdin refuses that list, they are named by their numbers; what is copied stays the same.
- Every built-in placeholder switched off in the copy is one request to Crowdin, and one entry in the activity of the new project. crowdin.com no longer shows the custom placeholders some projects were given before it kept them to Enterprise, so those are not copied.
- Webhooks of the source are not copied, and neither are its pending invitations on crowdin.com (MEMBER_PENDING_SKIPPED): no second email is sent to anyone. On Enterprise a person invited to the organization who has not accepted yet already has the project access they were given, and the members listing of the project has nothing that marks them, so the copy gives them the same access.