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

Bulk Replace Alternate Purchase Paths

Replace alternate purchase paths across content items. Partial success; default cap 500.

Replaces the full set of alternate purchase paths on each listed content item. Each item runs in its own transaction, so one invalid course is reported in failed without rolling back the rest.

An item whose entries array is empty clears every live path on that course. Omitting entries, sending null, or sending a non-array is a per-item failure — it does not silently clear.

A content item may appear at most once per request. Duplicate contentIds reject the whole payload with 400 before any item is written.

PUThttps://example.thoughtindustries.com/v3/admin/content/alternate-purchase-paths/bulk

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

Example request

curl -X PUT "https://{instance}.thoughtindustries.com/v3/admin/content/alternate-purchase-paths/bulk" \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
  "items": [
    {
      "contentId": "3a131ac3-1a74-420d-b4da-ae10b18b2c68",
      "entries": [
        {
          "label": "Subscribe for unlimited access",
          "targetType": "ti_item",
          "targetId": "7a6b5c4d-3e2f-1a0b-9c8d-7e6f5a4b3c2d"
        }
      ]
    },
    {
      "contentId": "9c8d7e6f-5a4b-3c2d-1e0f-9a8b7c6d5e4f",
      "entries": []
    }
  ]
}'

Parameters

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

NameTypeRequiredLocationDescription
itemsobject[]YesbodyPer-course replacement sets. Default cap 500 items (ALTERNATE_PURCHASE_PATH_BULK_MAX).
items[].contentIduuidYesbodySource course UUID. Must be unique within the request.
items[].entriesobject[]YesbodyFull replacement set. [] clears. Each object uses the same fields as Create.

The global ValidationPipe does not deeply validate each entry. Per-item errors land in failed[].error as a semicolon-joined string (the same static and purchasability checks as create). Up to 20 items are processed concurrently (ALTERNATE_PURCHASE_PATH_BULK_CONCURRENCY).

Example response

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

{
  "data": {
    "updated": [
      {
        "contentId": "3a131ac3-1a74-420d-b4da-ae10b18b2c68",
        "entries": [
          {
            "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-15T15:10:00.000Z",
            "updatedAt": "2026-09-15T15:10:00.000Z"
          }
        ]
      },
      {
        "contentId": "9c8d7e6f-5a4b-3c2d-1e0f-9a8b7c6d5e4f",
        "entries": []
      }
    ],
    "failed": []
  }
}

A typical per-item failure (the rest of the batch still commits):

{
  "data": {
    "updated": [],
    "failed": [
      {
        "contentId": "3a131ac3-1a74-420d-b4da-ae10b18b2c68",
        "error": "contentId does not name a content item in this company"
      }
    ]
  }
}

Other failed[].error strings you will see:

  • contentId must be a valid UUID
  • entries is required and must be an array (use [] to clear)
  • label is required
  • targetType must be one of: ti_item, external_url
  • targetId is required when targetType is ti_item
  • targetUrl is required when targetType is external_url
  • targetUrlTitle is required when targetType is external_url
  • targetId {uuid} does not resolve to a purchasable item (an active, priced course, learning path, subscription, or collection)

A missing contentId is reported as contentId: "(missing)".

Errors

More than 500 items (or the configured cap) rejects the whole request with 413:

{
  "code": "BULK_PAYLOAD_TOO_LARGE",
  "message": "Bulk update accepts at most 500 content items per request (received 501)",
  "category": "VALIDATION",
  "timestamp": "2026-09-15T15:10:00.000Z",
  "statusCode": 413,
  "metadata": {
    "limit": 500,
    "received": 501
  }
}

Duplicate contentIds reject the whole request with 400:

{
  "code": "VALIDATION_ERROR",
  "message": "Duplicate contentId in bulk payload",
  "category": "VALIDATION",
  "timestamp": "2026-09-15T15:10:00.000Z",
  "statusCode": 400,
  "metadata": {
    "errors": [
      "contentId 3a131ac3-1a74-420d-b4da-ae10b18b2c68 appears more than once; each may appear at most once"
    ]
  }
}

A non-array items value or an unrecognized top-level property is rejected by ValidationPipe as 400 with code: "BAD_REQUEST" and category: "CLIENT_ERROR". message names the field. timestamp, statusCode, and metadata.errors are present on the same body; see Create alternate purchase path for the shape.

A missing or invalid API key returns 401. A manager session without settings.alternatePurchasePaths returns 403.

Rate limit

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