Updates a content record — its title, comment controls, endorsement, CPD category, completion behaviour, and its set of items. Only content created through POST /contents or the authoring tool can be updated here; legacy content of other types answers 404. Archived content is updatable too: the change is applied and propagated, and the content stays archived.
This is a true partial update: send only the fields you want to change, and at least one. An empty item array and incrementVersion: false change nothing, so a body made of them alone answers 400, unless it also carries a thumbnail request. Items are edited through three arrays — addItems appends new items, updateItems edits existing ones by their item id, and removeItems deletes them by id. An item edited through updateItems keeps every field the change does not name, including its position in a layout authored in the UI. For each added upload item, and for each item whose file is replaced, the response returns a fresh signedPutUrls entry. This is also how you re-issue an upload URL that expired before the file was sent: send the item in updateItems with a new meta.uploadId UUID and its meta.name, then PUT the file to the new URL. Repeating the original uploadId is not a replacement and returns no new URL.
Updates are last-write-wins. An update reads the record, merges your changes into the record as it was read, and writes the result back. There is no version or If-Match precondition, so concurrent changes do not compose: two updates of the same record sent at the same time, or an update sent while the content is edited in the authoring UI, can silently overwrite one another. The later write is accepted, not answered with 409, so the caller is not told that it overwrote anything. Send changes to one record in sequence.
The item cap is per request, not per content. At most one upload item (elearning, video or image) plus one non-upload item (rich_text or url) can be added per call. Content that already holds more items than that is never rejected for it — add further items in further calls. A content can hold at most one e-learning item in total; adding a second one returns 409.
incrementVersion is irreversible. It bumps the content version and is valid only on published content (409 otherwise). Content keeps no version history, so an item replaced in the same request has its previous file deleted permanently. Treat it as replace-and-delete, not as a snapshot you can roll back to. Nothing bumps the version for you — you decide which change is version-worthy.
A CPD category needs endorsement. A cpdCategoryId is accepted only when the content is endorsed after the update: it is endorsed already, or the same request sends isOfficial: true. Otherwise the request is rejected with 400, the same as on create. The category must exist in your organisation (422 otherwise). isOfficial: false leaves a stored CPD category as it is; send cpdCategoryId: null to clear it. A request that does not send cpdCategoryId is never rejected for a category already stored on the content.
Skills, topics, tags and audiences cannot be set here. Skills and topics are set on create with publishWhenReady, or when the content is published. Sharing content with an audience has its own endpoints.
Thumbnail. Send thumbnail: { uploadId } with a UUID you generate to set or replace the content thumbnail, or to get a new upload URL after an earlier one expired. The response returns a matching signedPutUrls entry, valid for about 10 minutes; PUT the image bytes (JPEG, PNG, GIF or WebP) to it. The thumbnail is stored asynchronously, and a replaced thumbnail on published content leaves the status unchanged. A body that carries only thumbnail and authorRef is valid: it changes nothing on the record and only returns the URL, so no update is recorded. If the URL cannot be issued, that request fails (502) and nothing is changed. If the tag record of published content cannot be read, that response omits tags, skills and topics, the same as a read. The uploadId must differ from every item uploadId in the same request (400 otherwise).
authorRef is required on every update. It is your organisation's reference for the acting user, and the change is recorded against that user. It never changes the content's author. collaborationId is not accepted here: an update never re-attributes the content.
OAuth2 (Cognito) tokens need the api/write or api/all scope. API tokens are not scoped.
HTTP Basic authentication using your Tenant ID and API secret.
- Username is the Tenant ID (i.e.
eu-west-2_AbcdEfghI) - Password is the API secret you would have received.
You can also authenticate using OAuth 2.0 client credentials (oauth2 security scheme) instead of Basic auth.
OAuth 2.0 client credentials authentication.
Use the following token endpoints (replace :tenantId with your Tenant ID):
Public API access tokens
- Staging:
https://public.api.learnstaging.link/oauth2/token/:tenantId - Production:
https://public.api.learn.link/oauth2/token/:tenantId - Staging MEA:
https://public.api.meastaging.learn.tech/oauth2/token/:tenantId - Production MEA:
https://public.api.mea.learn.tech/oauth2/token/:tenantId
- Staging:
Webhooks access tokens
- Staging:
https://user.api.learnstaging.link/oauth2/token/:tenantId - Production:
https://user.api.learn.link/oauth2/token/:tenantId - Staging MEA:
https://user.api.meastaging.learn.tech/oauth2/token/:tenantId - Production MEA:
https://user.api.mea.learn.tech/oauth2/token/:tenantId
- Staging:
All access tokens must be sent using the Authorization: Bearer <access_token> header.
Scopes
For API access (non-webhooks), the following scopes are available:
api/all– Full read and write access to the API.api/read– Read-only access to the API.api/write– Write access to the API.
For webhooks, the following scopes are available:
api/webhooks– Access to webhook functionality.api/all– Full read and write access to the API, including webhooks.
Tokens must include appropriate scopes for the endpoints you wish to call.
Unique identifier of the content item
Retitle, replace the welcome text and add a video
Renames the content, rewrites the body of an existing rich text item by its id, and appends a video item. PUT the video bytes to the returned signedPutUrls entry.
{
"title": "Onboarding pack (2026)",
"authorRef": "UID30084022",
"updateItems": [
{
"id": "5c8936159ec2d00010cdd334",
"type": "rich_text",
"meta": {
"body": "Welcome to the 2026 onboarding pack!
"
}
}
],
"addItems": [
{
"type": "video",
"rank": 2,
"meta": {
"name": "intro.mp4",
"uploadId": "123e4567-e89b-42d3-a456-426614174001",
"canDownload": false
}
}
]
}Set or replace the thumbnail only
Changes nothing on the record. The response returns one signedPutUrls entry for the thumbnail; PUT the image bytes to it. The same request gets a new URL after an earlier one expired.
{
"authorRef": "UID30084022",
"thumbnail": {
"uploadId": "8b1e3d2a-4c5f-4e6a-9b7c-0d1e2f3a4b5c"
}
}The changes to apply. Every change field is optional — send only what you want to change, but at least one, or a thumbnail request. authorRef is validated in your organisation before anything is updated; it does not change the content's author. collaborationId is not accepted on an update: the content's author is never re-attributed, so the field could change nothing.
Your organisation's reference for the acting user. The update is recorded against this user. A suspended or deleted user is rejected.
Whether learners may comment on this content.
Whether the content is endorsed by your organisation. false leaves a stored CPD category as it is; send cpdCategoryId: null to clear it.
The CPD category to record this content against. null clears it. An id is only accepted when the content is endorsed after the update (400 otherwise), and it must exist in your organisation (422 otherwise).
How the content is completed.
Bump the content's version number. Valid only on published content (409 otherwise). This is not a snapshot — content keeps no version history, so any item replaced in this request has its previous file deleted permanently.
Items to append. The per-request cap applies to this array only, not to the resulting document: at most one upload item plus one non-upload item. Each rank must be unique within this array (422 otherwise) and must not collide with the rank of an item already on the content (409 otherwise).
The display position of the item.
A short description of the item.
The rich text body. May contain HTML.
A public HTTP(S) address. The link preview is generated asynchronously.
The package file name, for example course.zip.
A client-generated UUID for the upload. The response echoes it in signedPutUrls.
The package format. Normally detected during processing — supply it only when known.
The file kind. The file extension in meta.name must match it.
The file name, for example intro.mp4 or cover.png.
A client-generated UUID for the upload. The response echoes it in signedPutUrls.
Whether learners can download the file.
Existing items to change, identified by their item id. The type must match the stored item — an item cannot change type. Fields the change does not name keep their stored values. A rank that collides with the rank of another item that survives this request answers 409, the same as an added item's rank — ranks are checked as a set once the whole change is applied.
The id of the item to change.
The display position of the item.
A short description of the item.
The rich text body. May contain HTML.
The id of the item to change.
A public HTTP(S) address. The link preview is regenerated asynchronously.
The id of the item to change.
The replacement package file name, for example course.zip. Send it with uploadId. The two fields go together and either one on its own is rejected with a 400: a name has no effect without a replacement package.
A client-generated UUID for the replacement upload. The response echoes it in signedPutUrls.
Whether learners can download the file.
The id of the item to change.
The stored item's kind. The file extension in meta.name must match it.
The replacement file name, for example intro.mp4 or cover.png. Send it with uploadId. The two fields go together and either one on its own is rejected with a 400: a name has no effect without a replacement file.
A client-generated UUID for the replacement upload. The response echoes it in signedPutUrls.
Whether learners can download the file.
The ids of items to delete. Removing an upload item deletes its stored file permanently. An id that does not match an item on this content is rejected with 422.
Asks for a thumbnail upload URL. The content thumbnail is uploaded like an item file: give the upload a UUID here and PUT the image bytes (JPEG, PNG, GIF or WebP) to the signedPutUrls entry that echoes it. The uploadId is not stored; it only pairs this request with its response entry, so it must differ from every item uploadId in the same request (400 otherwise). A content has one thumbnail, and a later upload replaces it. Processing is asynchronous: the thumbnail counts for readiness once the image has been resized and stored.
A UUID you generate for this upload.
The updated content, with fresh upload URLs for any added or replaced upload items
The updated content with its presigned upload URLs. In the rare case that the content was updated but could not be read back, the response is the minimal form: id, status and signedPutUrls only. The update was applied in both cases — do not retry the request. A full body also satisfies the minimal shape, so this is anyOf.
Unique identifier for the content
Title of the content
Detailed description of the content, may contain HTML
List of tags associated with this content. Omitted when the tag source cannot be read, so an absent field means "tags unavailable" rather than "no tags".
The tags entries that the taxonomy marks as a skill. Additive: every name is also in tags. A tag the taxonomy does not hold is in neither skills nor topics. Both fields are absent when the taxonomy cannot be read.
The tags entries that the taxonomy marks as a topic. Additive: every name is also in tags. A tag the taxonomy does not hold is in neither topics nor skills. Both fields are absent when the taxonomy cannot be read.
The kind of artifact associated with this content record.
Courses are represented as pathway. Pathways will be served by a dedicated Pathways API in a future release and will stop appearing on this endpoint at that point, so treat pathway as temporary here.
Timestamp when the content was created
Timestamp when the content was last updated
User ID who authored this content. Nullable for auto-curated content.
Whether the content has been recognized for its importance or validity by the customer organization
The CPD category the content is recorded against, or null when it has none. Present when a record is read by its id or returned by a v2 create or update, for content held by the content source that carries CPD data. The field is not present in the results of the list endpoint, nor on a record that exists only in the legacy content source.
The expected time to complete the content. Null for Event content types. For SAP-imported content, this value may reflect a system default rather than a precise measured duration.
Duration in the specified unit. Null when no duration is available (e.g. pathways).
The unit of the duration value. Always 'minutes'.
Chronological history of actions on this content (from content-service). May be empty when there is no recorded history.
Type of change or event recorded for the content.
When the action occurred.
Kind of actor (for example user or system).
Identifier or value associated with the actor.
Last actor who updated the content, derived from history (or author fallback). Null when neither history nor author resolves.
User ID or service identifier.
Email from the user view when available.
External reference (e.g. HR id) when stored on the user view.
The content provider. Defaults to Thrive for natively created content.
Provider identifier key.
Human-readable provider display name.
Lifecycle status of the content. A legacy (content-service) record derives the value: archived content reports archived, content with a publish time reports published, and all other content reports created. A content-core record maps its lifecycle status onto the same values, where draft and ready both report created. deleted is a filter value only and is never returned: a legacy record returned by a deleted status filter reports its pre-deletion state, and a deleted content-core record is physically gone.
The items that make up a content record, with per-item processing state. Only GET /contents/{id} returns this field, and only for records served from content-core; the GET /contents list never includes it.
One item within a content record, with its processing state.
Unique identifier for the item
The item type
The item's position within the content, lowest first
A short label for the item, shown to learners
The item's processing state. An item must reach ok before the content can become ready.
Why processing failed, when status is error
One entry for each upload item added, and each item whose file was replaced, in this request, plus one for the thumbnail when the request carried thumbnail. Empty when the request carried none of them. In the rare case that the thumbnail URL could not be issued after an update, its entry is missing while the update was still applied; send a PATCH with thumbnail to get one. A request that carries only thumbnail applies no update, so it answers an error instead when the URL cannot be issued.
A presigned URL to PUT one upload's bytes to: an item file, or the content thumbnail image.
The uploadId you supplied for the item, or for the thumbnail, this URL belongs to
The presigned URL. Time-limited (about 10 minutes) — upload promptly.
Unique identifier for the content
On a create, one entry for each upload item in the request. On an update, one entry for each upload item added and each item whose file was replaced. Plus one for the thumbnail when the request carried thumbnail. Empty when the request carried none.
A presigned URL to PUT one upload's bytes to: an item file, or the content thumbnail image.
The uploadId you supplied for the item, or for the thumbnail, this URL belongs to
The presigned URL. Time-limited (about 10 minutes) — upload promptly.
Bad Request — a cross-field rule failed (no update in the body — an empty item array or incrementVersion: false does not count, more than one upload or non-upload item added, an item id in both updateItems and removeItems, on_elearning_completion with a web format item, cpdCategoryId on content that is not endorsed after the update, a thumbnail uploadId that repeats an item uploadId), or the upstream content source rejected the payload (see violations when present)
The error envelope of the V2 endpoints.
Present on field validation failures. One entry per failed rule.
One failed validation rule.
The dotted path of the field that failed, for example items.0.meta.body. Empty for a body-level rule.
Why the field failed.
Unauthorized
Unauthorized
Forbidden — the OAuth2 token does not carry the api/write or api/all scope
The error envelope of the V2 endpoints.
Present on field validation failures. One entry per failed rule.
One failed validation rule.
The dotted path of the field that failed, for example items.0.meta.body. Empty for a body-level rule.
Why the field failed.
Not Found — the id does not match a content record in your organisation, or the record is legacy content of another type
The error envelope of the V2 endpoints.
Present on field validation failures. One entry per failed rule.
One failed validation rule.
The dotted path of the field that failed, for example items.0.meta.body. Empty for a body-level rule.
Why the field failed.
Conflict — incrementVersion was sent for content that is not published, the update would give the content a second e-learning item, an added or updated item's rank collides with the rank of another item on the content, the completionType does not fit the resulting items (for example on_elearning_completion on content that has no e-learning item), or the content's tag record does not exist yet. The last case applies only to published content, and clears by itself within moments of a publish; if it persists for a record published long ago, the record needs support.
The error envelope of the V2 endpoints.
Present on field validation failures. One entry per failed rule.
One failed validation rule.
The dotted path of the field that failed, for example items.0.meta.body. Empty for a body-level rule.
Why the field failed.
Unsupported Media Type — Content-Type must be application/json
The error envelope of the V2 endpoints.
Present on field validation failures. One entry per failed rule.
One failed validation rule.
The dotted path of the field that failed, for example items.0.meta.body. Empty for a body-level rule.
Why the field failed.
Unprocessable Content — a field failed validation (see violations), authorRef does not match an active user in this organisation, an updateItems or removeItems entry does not match an item on this content, or cpdCategoryId does not match a CPD category in this organisation
The error envelope of the V2 endpoints.
Present on field validation failures. One entry per failed rule.
One failed validation rule.
The dotted path of the field that failed, for example items.0.meta.body. Empty for a body-level rule.
Why the field failed.
Too Many Requests — the upstream content source is rate limiting this organisation. Retry after the Retry-After header when it is present.
The error envelope of the V2 endpoints.
Present on field validation failures. One entry per failed rule.
One failed validation rule.
The dotted path of the field that failed, for example items.0.meta.body. Empty for a body-level rule.
Why the field failed.
Internal Server Error — the request failed inside the API service. When the message says the content was not updated, nothing was sent upstream. Otherwise the content may have been updated — read it back before you retry.
The error envelope of the V2 endpoints.
Present on field validation failures. One entry per failed rule.
One failed validation rule.
The dotted path of the field that failed, for example items.0.meta.body. Empty for a body-level rule.
Why the field failed.
Bad Gateway. Read the message to learn what the state of the content is. When the message says the tags or the CPD category could not be resolved or verified, the check ran before the write and the content was not updated. When the message names an upstream content source failure, the write had already started and the content may have been updated — read it back before you retry, because a repeat with incrementVersion set raises the version again and can delete a second replaced file.
The error envelope of the V2 endpoints.
Present on field validation failures. One entry per failed rule.
One failed validation rule.
The dotted path of the field that failed, for example items.0.meta.body. Empty for a body-level rule.
Why the field failed.
An unexpected error has occurred
When any default error occurs it may be a system failure and persistent errors may require support.
The server is unable to process the request