Skip to main content
https://.thoughtindustries.com

Clone Client

Copies an existing Panorama (client) into a new client asynchronously, returning a background job to poll for completion

Copies an existing Panorama (client) into a new client within the same company. The copy runs asynchronously: the endpoint returns a BackgroundJob immediately, then you poll that job until it finishes.

POSThttps://example.thoughtindustries.com/incoming/v2/clients/:id/clone

Requires an API key whose role includes the clients.edit permission.

Example request

curl -X POST "https://example.thoughtindustries.com/incoming/v2/clients/:id/clone" \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "Acme West",
  "slug": "acme-west"
}'

Parameters

NameTypeRequiredLocationDescription
iduuidYespathID of the source Panorama (client) to copy.
namestringYesbodyDisplay name for the new client.
slugstringNobodyURL path for the new client. If omitted (or empty), a slug is derived from name. Must be unique within the company.

Slug rules

  • If you supply slug, it must match ^[a-z0-9-]{0,255}$ — lowercase letters, digits, and hyphens only, up to 255 characters. A supplied slug is validated as-is (it is not auto-transformed). A value that does not match is rejected with an HTTP 400. Note: the API does not currently return a slug-specific message for this case — the validation error is masked by a generic catch-all, so the response body is { "errors": [{ "message": "A processing error occurred. Please refresh the page and try again." }] } (see Error responses below).
  • If you omit slug (or send an empty/whitespace value), it is generated from name: whitespace, underscores, and slashes become hyphens; all other non-alphanumeric characters are removed; consecutive hyphens are collapsed; leading and trailing hyphens are trimmed; the result is lowercased and truncated to 255 characters.
  • Slugs are unique per company. If the resulting slug already belongs to another client in the company, the request fails.

Example response

The response is a BackgroundJob. The clone is not finished when this returns — poll the job (see below) until status is complete.

{
  "data": {
    "APICloneClient": {
      "id": "00000000-0000-0000-0000-000000000000",
      "description": "Copying 3f9a2b18-7c4d-4e2a-9b1f-0c5d6e7f8a90 to new client Acme West.",
      "errorMessage": null,
      "status": "queued"
    }
  }
}

Response fields

FieldTypeDescription
iduuidID of the background job. Use it to poll for completion.
descriptionstringHuman-readable summary of the job.
errorMessagestringError details if the job failed; null or absent on success.
statusBackgroundJobStatusOne of queued, processing, complete, failed.

Polling for completion

Poll the background job with GET /v2/jobs/:id, passing the id returned above. It returns the same BackgroundJob shape; status transitions queuedprocessingcomplete (or failed, with errorMessage populated).

curl "https://example.thoughtindustries.com/incoming/v2/jobs/:id" \
  -H 'Authorization: Bearer YOUR_API_KEY'

Once the job is complete, find the new client by its slug:

curl "https://example.thoughtindustries.com/incoming/v2/clients?query=acme-west" \
  -H 'Authorization: Bearer YOUR_API_KEY'

Error responses

StatusBodyWhen
400{ "valid": false, "errors": ["Missing required parameter."] }name is missing or empty (or the derived slug is empty because name contains no slug-safe characters and no slug was supplied).
400{ "valid": false, "errors": ["A Client already exists with that path."] }The slug is already used by another client in the company.
400{ "valid": false, "errors": ["A job for cloning this client with the specified path is already queued."] }A clone job for this source client with the same slug is already in flight.
400{ "errors": [{ "message": "A processing error occurred. Please refresh the page and try again." }] }The supplied slug does not match ^[a-z0-9-]{0,255}$. The Slug scalar's validation error is masked by a generic catch-all, so no slug-specific message is returned. Note this envelope differs from the other 400s above (it is { errors: [{ message }] }, not { valid, errors }).
403{ "error": "<message>" }The API key's role lacks the clients.edit permission.

What gets copied

  • Name and slug from the request.
  • The new client is flagged generated: true.
  • Course tags (courseTags).
  • panorama and disabled flags.
  • Feature toggles: enableSegmentation, enableDiscussions, enableCommunitiesSegmentation, enableBranding, enableEcommerce, enableOnboardingSurvey, enableGlobalLinks, enableNavLinks, enableRecommendationAssessment.
  • Language settings: defaultLanguage, languageSelectorEnabled, languages, autoFilterForSelectedLanguage.
  • Provisioned content (for a Panorama source): courses, learningPaths, purchasableContentIds, purchasableTagIds.
  • Licenses / sublicenses: every license on the source client is copied (with new IDs), including its course tags and expiration; parent-license relationships and the client's primary license are remapped to the copies.
  • Blocks: appearanceBlock, emailLayoutBlock, catalogBlock, ssoBlock (copied at both the client and per-license level); a fresh appearanceHash is generated.
  • Layouts: redemptionLayout, dashboardLayout (copied at both the client and per-license level); layout widget IDs are regenerated.
  • Seat usage is recomputed for the new client.

What is not copied

  • sku
  • seatsLimit
  • Client-level licenseEndDate
  • notificationEmails
  • enableCustomEmailSettings — the emailLayoutBlock is copied, but because this toggle is not carried over, the copied email branding is inactive on the new client.
  • enableContentDetailPage
  • enableLicenseDashboards
  • enableCreditPurchasing
  • customHost
  • Learners and their enrollments
  • Onboarding survey responses (only the enableOnboardingSurvey toggle is copied, not learner responses)