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

Create Alternate Purchase Path

Create an alternate purchase path on a content item. TI item or external URL target.

Creates one alternate purchase path on a content item. Use this for a single write. To replace the full set across many courses, use Bulk replace alternate purchase paths.

label is rich-text HTML. The server sanitizes it on write to a, b, strong, i, em, u, br, p, and span. Links keep http/https only and are rewritten to target="_blank" with rel="noopener noreferrer". Disallowed tags are stripped; their text is kept.

Exactly one target kind is stored: ti_item (an active, priced course, learning path, subscription, or collection) or external_url (http/https plus targetUrlTitle).

POSThttps://example.thoughtindustries.com/v3/admin/content/:contentId/alternate-purchase-paths

This endpoint is served by the v3 API. Its full path is https://{instance}.thoughtindustries.com/v3/admin/content/{contentId}/alternate-purchase-paths, not the /incoming/v2 base URL used by the rest of this reference. Use the full URL shown in the examples below.

Example request — TI item target

curl -X POST "https://{instance}.thoughtindustries.com/v3/admin/content/3a131ac3-1a74-420d-b4da-ae10b18b2c68/alternate-purchase-paths" \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
  "label": "Subscribe for unlimited access",
  "targetType": "ti_item",
  "targetId": "7a6b5c4d-3e2f-1a0b-9c8d-7e6f5a4b3c2d",
  "hidePrimaryCta": false
}'

Example request — external URL target

curl -X POST "https://{instance}.thoughtindustries.com/v3/admin/content/3a131ac3-1a74-420d-b4da-ae10b18b2c68/alternate-purchase-paths" \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
  "label": "Buy from our store",
  "targetType": "external_url",
  "targetUrl": "https://example.com/checkout",
  "targetUrlTitle": "Example Store",
  "hidePrimaryCta": true
}'

Parameters

Requires the settings.alternatePurchasePaths permission. A company API key satisfies this.

NameTypeRequiredLocationDescription
contentIduuidYespathSource course UUID. Learning path IDs are rejected (404).
labelstringYesbodyCTA label. Maximum 2000 characters after sanitize.
targetTypestringYesbodyti_item or external_url.
targetIduuidWhen ti_itembodyTarget item UUID. Must be purchasable (active + priced).
targetUrlurlWhen external_urlbodyhttp or https only. Maximum 2048 characters. Reachability is not checked.
targetUrlTitlestringWhen external_urlbodyDisplay title. Maximum 255 characters. Must be empty for ti_item.
displayOrderintegerNobodySort key. Default 0. Must be a non-negative integer.
hidePrimaryCtabooleanNobodyDefault false.

targetId kinds the write will accept: course, learningPath, subscription (any purchasable, priced bundle in ti.bundles), and collection (a purchasable, priced discount group). Confirm a candidate with Filter purchasable targets before attaching it.

Example response (201)

Note the data wrapper — v3 endpoints nest their payload under data.

{
  "data": {
    "id": "443d8182-d461-4568-a777-918ca729fd85",
    "courseId": "3a131ac3-1a74-420d-b4da-ae10b18b2c68",
    "displayOrder": 0,
    "label": "Subscribe for unlimited access",
    "targetType": "ti_item",
    "targetId": "7a6b5c4d-3e2f-1a0b-9c8d-7e6f5a4b3c2d",
    "targetUrl": null,
    "targetUrlTitle": null,
    "hidePrimaryCta": false,
    "createdAt": "2026-09-15T14:22:01.000Z",
    "updatedAt": "2026-09-15T14:22:01.000Z"
  }
}

Errors

Cross-field validation (wrong target pairing, empty label after sanitize, or a non-purchasable targetId) returns 400:

{
  "code": "VALIDATION_ERROR",
  "message": "Invalid alternate purchase path",
  "category": "VALIDATION",
  "timestamp": "2026-09-15T14:22:01.000Z",
  "statusCode": 400,
  "metadata": {
    "errors": [
      "targetId 7a6b5c4d-3e2f-1a0b-9c8d-7e6f5a4b3c2d does not resolve to a purchasable item (an active, priced course, learning path, subscription, or collection)"
    ]
  }
}

An unknown or cross-company contentId returns 404 with message: "Content item with identifier \"…\" was not found" — see List alternate purchase paths.

Malformed DTO values — a non-enum targetType, a non-UUID targetId, a non-http(s) targetUrl, an over-length field, or an unrecognized property (whitelist + forbidNonWhitelisted are on) — are rejected by the global ValidationPipe as 400 with code: "BAD_REQUEST" and category: "CLIENT_ERROR". The class-validator messages are surfaced: message is the failed constraints joined with ; , and metadata.errors lists them individually.

{
  "code": "BAD_REQUEST",
  "message": "targetType must be one of the following values: ti_item, external_url",
  "category": "CLIENT_ERROR",
  "timestamp": "2026-09-15T14:22:01.000Z",
  "statusCode": 400,
  "metadata": {
    "errors": [
      "targetType must be one of the following values: ti_item, external_url"
    ]
  }
}

A missing or invalid API key returns 401 with code: "UNAUTHENTICATED". A manager session without settings.alternatePurchasePaths returns 403 with code: "FORBIDDEN" and message: "Insufficient permissions: settings.alternatePurchasePaths required".

Rate limit

60 requests per 60 seconds, counted per instance. This limit is specific to this endpoint and is independent of the per-family rate limits that apply to /incoming/v2 endpoints.