curl --request POST \
--url https://prod.skillsdbnext.com/api/v1/learning-items/with-file \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"title": "Forklift Safety Refresher",
"typeId": "1",
"fileName": "forklift-refresher.pdf",
"description": "Annual refresher for licensed operators.",
"url": "https://learn.example.com/forklift",
"durationHours": 2.5,
"cost": 120,
"costDescription": "Per seat",
"providerId": "3",
"formatId": "2",
"audienceId": "1",
"courseLevelId": "4",
"referenceId": "LMS-4471",
"labelIds": [
"<string>"
],
"skillIds": [
"<string>"
]
}
'import requests
url = "https://prod.skillsdbnext.com/api/v1/learning-items/with-file"
payload = {
"title": "Forklift Safety Refresher",
"typeId": "1",
"fileName": "forklift-refresher.pdf",
"description": "Annual refresher for licensed operators.",
"url": "https://learn.example.com/forklift",
"durationHours": 2.5,
"cost": 120,
"costDescription": "Per seat",
"providerId": "3",
"formatId": "2",
"audienceId": "1",
"courseLevelId": "4",
"referenceId": "LMS-4471",
"labelIds": ["<string>"],
"skillIds": ["<string>"]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
title: 'Forklift Safety Refresher',
typeId: '1',
fileName: 'forklift-refresher.pdf',
description: 'Annual refresher for licensed operators.',
url: 'https://learn.example.com/forklift',
durationHours: 2.5,
cost: 120,
costDescription: 'Per seat',
providerId: '3',
formatId: '2',
audienceId: '1',
courseLevelId: '4',
referenceId: 'LMS-4471',
labelIds: ['<string>'],
skillIds: ['<string>']
})
};
fetch('https://prod.skillsdbnext.com/api/v1/learning-items/with-file', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://prod.skillsdbnext.com/api/v1/learning-items/with-file",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'title' => 'Forklift Safety Refresher',
'typeId' => '1',
'fileName' => 'forklift-refresher.pdf',
'description' => 'Annual refresher for licensed operators.',
'url' => 'https://learn.example.com/forklift',
'durationHours' => 2.5,
'cost' => 120,
'costDescription' => 'Per seat',
'providerId' => '3',
'formatId' => '2',
'audienceId' => '1',
'courseLevelId' => '4',
'referenceId' => 'LMS-4471',
'labelIds' => [
'<string>'
],
'skillIds' => [
'<string>'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://prod.skillsdbnext.com/api/v1/learning-items/with-file"
payload := strings.NewReader("{\n \"title\": \"Forklift Safety Refresher\",\n \"typeId\": \"1\",\n \"fileName\": \"forklift-refresher.pdf\",\n \"description\": \"Annual refresher for licensed operators.\",\n \"url\": \"https://learn.example.com/forklift\",\n \"durationHours\": 2.5,\n \"cost\": 120,\n \"costDescription\": \"Per seat\",\n \"providerId\": \"3\",\n \"formatId\": \"2\",\n \"audienceId\": \"1\",\n \"courseLevelId\": \"4\",\n \"referenceId\": \"LMS-4471\",\n \"labelIds\": [\n \"<string>\"\n ],\n \"skillIds\": [\n \"<string>\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://prod.skillsdbnext.com/api/v1/learning-items/with-file")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"title\": \"Forklift Safety Refresher\",\n \"typeId\": \"1\",\n \"fileName\": \"forklift-refresher.pdf\",\n \"description\": \"Annual refresher for licensed operators.\",\n \"url\": \"https://learn.example.com/forklift\",\n \"durationHours\": 2.5,\n \"cost\": 120,\n \"costDescription\": \"Per seat\",\n \"providerId\": \"3\",\n \"formatId\": \"2\",\n \"audienceId\": \"1\",\n \"courseLevelId\": \"4\",\n \"referenceId\": \"LMS-4471\",\n \"labelIds\": [\n \"<string>\"\n ],\n \"skillIds\": [\n \"<string>\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://prod.skillsdbnext.com/api/v1/learning-items/with-file")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"title\": \"Forklift Safety Refresher\",\n \"typeId\": \"1\",\n \"fileName\": \"forklift-refresher.pdf\",\n \"description\": \"Annual refresher for licensed operators.\",\n \"url\": \"https://learn.example.com/forklift\",\n \"durationHours\": 2.5,\n \"cost\": 120,\n \"costDescription\": \"Per seat\",\n \"providerId\": \"3\",\n \"formatId\": \"2\",\n \"audienceId\": \"1\",\n \"courseLevelId\": \"4\",\n \"referenceId\": \"LMS-4471\",\n \"labelIds\": [\n \"<string>\"\n ],\n \"skillIds\": [\n \"<string>\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"data": {
"item": {
"id": "512",
"title": "Forklift Safety Refresher",
"description": "Annual refresher for licensed operators.",
"url": "https://learn.example.com/forklift",
"durationHours": 2.5,
"cost": 120,
"costDescription": "Per seat, invoiced quarterly",
"type": {
"id": "3",
"name": "Online course"
},
"provider": {
"id": "3",
"name": "Online course"
},
"format": {
"id": "3",
"name": "Online course"
},
"audience": {
"id": "3",
"name": "Online course"
},
"courseLevel": {
"id": "3",
"name": "Online course"
},
"imageUrl": "https://cdn.example.com/forklift.png",
"hasFile": true,
"referenceId": "LMS-4471",
"labels": [
{
"id": "77",
"name": "Forklift Operation"
}
],
"skillNames": [
"Forklift Operation"
],
"careerNames": [
"Warehouse Operator"
],
"assignedCount": 40,
"inProgressCount": 12,
"completedCount": 25,
"averageRating": 1.6,
"createdAt": "2026-01-12T08:30:00.000Z",
"updatedAt": null
},
"uploadUrl": "<string>"
}
}{
"type": "https://help.skillsdb.com/api-docs/errors/validation-error",
"title": "Validation failed",
"status": 400,
"detail": "limit must be between 1 and 200",
"instance": "/api/v1/people",
"requestId": "0192c2f1-6f4e-7ab3-b1c4-5f6a7b8c9d0e",
"errors": [
{
"field": "email",
"code": "max_length",
"message": "email must be shorter than or equal to 75 characters"
}
],
"error": "trial_expired",
"upgrade_url": "https://app.skillsdb.com/settings/billing"
}{
"type": "https://help.skillsdb.com/api-docs/errors/validation-error",
"title": "Validation failed",
"status": 400,
"detail": "limit must be between 1 and 200",
"instance": "/api/v1/people",
"requestId": "0192c2f1-6f4e-7ab3-b1c4-5f6a7b8c9d0e",
"errors": [
{
"field": "email",
"code": "max_length",
"message": "email must be shorter than or equal to 75 characters"
}
],
"error": "trial_expired",
"upgrade_url": "https://app.skillsdb.com/settings/billing"
}{
"type": "https://help.skillsdb.com/api-docs/errors/validation-error",
"title": "Validation failed",
"status": 400,
"detail": "limit must be between 1 and 200",
"instance": "/api/v1/people",
"requestId": "0192c2f1-6f4e-7ab3-b1c4-5f6a7b8c9d0e",
"errors": [
{
"field": "email",
"code": "max_length",
"message": "email must be shorter than or equal to 75 characters"
}
],
"error": "trial_expired",
"upgrade_url": "https://app.skillsdb.com/settings/billing"
}{
"type": "https://help.skillsdb.com/api-docs/errors/validation-error",
"title": "Validation failed",
"status": 400,
"detail": "limit must be between 1 and 200",
"instance": "/api/v1/people",
"requestId": "0192c2f1-6f4e-7ab3-b1c4-5f6a7b8c9d0e",
"errors": [
{
"field": "email",
"code": "max_length",
"message": "email must be shorter than or equal to 75 characters"
}
],
"error": "trial_expired",
"upgrade_url": "https://app.skillsdb.com/settings/billing"
}{
"type": "https://help.skillsdb.com/api-docs/errors/validation-error",
"title": "Validation failed",
"status": 400,
"detail": "limit must be between 1 and 200",
"instance": "/api/v1/people",
"requestId": "0192c2f1-6f4e-7ab3-b1c4-5f6a7b8c9d0e",
"errors": [
{
"field": "email",
"code": "max_length",
"message": "email must be shorter than or equal to 75 characters"
}
],
"error": "trial_expired",
"upgrade_url": "https://app.skillsdb.com/settings/billing"
}{
"type": "https://help.skillsdb.com/api-docs/errors/validation-error",
"title": "Validation failed",
"status": 400,
"detail": "limit must be between 1 and 200",
"instance": "/api/v1/people",
"requestId": "0192c2f1-6f4e-7ab3-b1c4-5f6a7b8c9d0e",
"errors": [
{
"field": "email",
"code": "max_length",
"message": "email must be shorter than or equal to 75 characters"
}
],
"error": "trial_expired",
"upgrade_url": "https://app.skillsdb.com/settings/billing"
}{
"type": "https://help.skillsdb.com/api-docs/errors/validation-error",
"title": "Validation failed",
"status": 400,
"detail": "limit must be between 1 and 200",
"instance": "/api/v1/people",
"requestId": "0192c2f1-6f4e-7ab3-b1c4-5f6a7b8c9d0e",
"errors": [
{
"field": "email",
"code": "max_length",
"message": "email must be shorter than or equal to 75 characters"
}
],
"error": "trial_expired",
"upgrade_url": "https://app.skillsdb.com/settings/billing"
}Create a learning item with a file
The two-step file path: the item is created and a pre-signed upload URL for fileName is returned. PUT the file’s bytes to that URL; the item’s hasFile becomes true once the upload completes.
Requires scope: learning:write.
curl --request POST \
--url https://prod.skillsdbnext.com/api/v1/learning-items/with-file \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"title": "Forklift Safety Refresher",
"typeId": "1",
"fileName": "forklift-refresher.pdf",
"description": "Annual refresher for licensed operators.",
"url": "https://learn.example.com/forklift",
"durationHours": 2.5,
"cost": 120,
"costDescription": "Per seat",
"providerId": "3",
"formatId": "2",
"audienceId": "1",
"courseLevelId": "4",
"referenceId": "LMS-4471",
"labelIds": [
"<string>"
],
"skillIds": [
"<string>"
]
}
'import requests
url = "https://prod.skillsdbnext.com/api/v1/learning-items/with-file"
payload = {
"title": "Forklift Safety Refresher",
"typeId": "1",
"fileName": "forklift-refresher.pdf",
"description": "Annual refresher for licensed operators.",
"url": "https://learn.example.com/forklift",
"durationHours": 2.5,
"cost": 120,
"costDescription": "Per seat",
"providerId": "3",
"formatId": "2",
"audienceId": "1",
"courseLevelId": "4",
"referenceId": "LMS-4471",
"labelIds": ["<string>"],
"skillIds": ["<string>"]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
title: 'Forklift Safety Refresher',
typeId: '1',
fileName: 'forklift-refresher.pdf',
description: 'Annual refresher for licensed operators.',
url: 'https://learn.example.com/forklift',
durationHours: 2.5,
cost: 120,
costDescription: 'Per seat',
providerId: '3',
formatId: '2',
audienceId: '1',
courseLevelId: '4',
referenceId: 'LMS-4471',
labelIds: ['<string>'],
skillIds: ['<string>']
})
};
fetch('https://prod.skillsdbnext.com/api/v1/learning-items/with-file', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://prod.skillsdbnext.com/api/v1/learning-items/with-file",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'title' => 'Forklift Safety Refresher',
'typeId' => '1',
'fileName' => 'forklift-refresher.pdf',
'description' => 'Annual refresher for licensed operators.',
'url' => 'https://learn.example.com/forklift',
'durationHours' => 2.5,
'cost' => 120,
'costDescription' => 'Per seat',
'providerId' => '3',
'formatId' => '2',
'audienceId' => '1',
'courseLevelId' => '4',
'referenceId' => 'LMS-4471',
'labelIds' => [
'<string>'
],
'skillIds' => [
'<string>'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://prod.skillsdbnext.com/api/v1/learning-items/with-file"
payload := strings.NewReader("{\n \"title\": \"Forklift Safety Refresher\",\n \"typeId\": \"1\",\n \"fileName\": \"forklift-refresher.pdf\",\n \"description\": \"Annual refresher for licensed operators.\",\n \"url\": \"https://learn.example.com/forklift\",\n \"durationHours\": 2.5,\n \"cost\": 120,\n \"costDescription\": \"Per seat\",\n \"providerId\": \"3\",\n \"formatId\": \"2\",\n \"audienceId\": \"1\",\n \"courseLevelId\": \"4\",\n \"referenceId\": \"LMS-4471\",\n \"labelIds\": [\n \"<string>\"\n ],\n \"skillIds\": [\n \"<string>\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://prod.skillsdbnext.com/api/v1/learning-items/with-file")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"title\": \"Forklift Safety Refresher\",\n \"typeId\": \"1\",\n \"fileName\": \"forklift-refresher.pdf\",\n \"description\": \"Annual refresher for licensed operators.\",\n \"url\": \"https://learn.example.com/forklift\",\n \"durationHours\": 2.5,\n \"cost\": 120,\n \"costDescription\": \"Per seat\",\n \"providerId\": \"3\",\n \"formatId\": \"2\",\n \"audienceId\": \"1\",\n \"courseLevelId\": \"4\",\n \"referenceId\": \"LMS-4471\",\n \"labelIds\": [\n \"<string>\"\n ],\n \"skillIds\": [\n \"<string>\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://prod.skillsdbnext.com/api/v1/learning-items/with-file")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"title\": \"Forklift Safety Refresher\",\n \"typeId\": \"1\",\n \"fileName\": \"forklift-refresher.pdf\",\n \"description\": \"Annual refresher for licensed operators.\",\n \"url\": \"https://learn.example.com/forklift\",\n \"durationHours\": 2.5,\n \"cost\": 120,\n \"costDescription\": \"Per seat\",\n \"providerId\": \"3\",\n \"formatId\": \"2\",\n \"audienceId\": \"1\",\n \"courseLevelId\": \"4\",\n \"referenceId\": \"LMS-4471\",\n \"labelIds\": [\n \"<string>\"\n ],\n \"skillIds\": [\n \"<string>\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"data": {
"item": {
"id": "512",
"title": "Forklift Safety Refresher",
"description": "Annual refresher for licensed operators.",
"url": "https://learn.example.com/forklift",
"durationHours": 2.5,
"cost": 120,
"costDescription": "Per seat, invoiced quarterly",
"type": {
"id": "3",
"name": "Online course"
},
"provider": {
"id": "3",
"name": "Online course"
},
"format": {
"id": "3",
"name": "Online course"
},
"audience": {
"id": "3",
"name": "Online course"
},
"courseLevel": {
"id": "3",
"name": "Online course"
},
"imageUrl": "https://cdn.example.com/forklift.png",
"hasFile": true,
"referenceId": "LMS-4471",
"labels": [
{
"id": "77",
"name": "Forklift Operation"
}
],
"skillNames": [
"Forklift Operation"
],
"careerNames": [
"Warehouse Operator"
],
"assignedCount": 40,
"inProgressCount": 12,
"completedCount": 25,
"averageRating": 1.6,
"createdAt": "2026-01-12T08:30:00.000Z",
"updatedAt": null
},
"uploadUrl": "<string>"
}
}{
"type": "https://help.skillsdb.com/api-docs/errors/validation-error",
"title": "Validation failed",
"status": 400,
"detail": "limit must be between 1 and 200",
"instance": "/api/v1/people",
"requestId": "0192c2f1-6f4e-7ab3-b1c4-5f6a7b8c9d0e",
"errors": [
{
"field": "email",
"code": "max_length",
"message": "email must be shorter than or equal to 75 characters"
}
],
"error": "trial_expired",
"upgrade_url": "https://app.skillsdb.com/settings/billing"
}{
"type": "https://help.skillsdb.com/api-docs/errors/validation-error",
"title": "Validation failed",
"status": 400,
"detail": "limit must be between 1 and 200",
"instance": "/api/v1/people",
"requestId": "0192c2f1-6f4e-7ab3-b1c4-5f6a7b8c9d0e",
"errors": [
{
"field": "email",
"code": "max_length",
"message": "email must be shorter than or equal to 75 characters"
}
],
"error": "trial_expired",
"upgrade_url": "https://app.skillsdb.com/settings/billing"
}{
"type": "https://help.skillsdb.com/api-docs/errors/validation-error",
"title": "Validation failed",
"status": 400,
"detail": "limit must be between 1 and 200",
"instance": "/api/v1/people",
"requestId": "0192c2f1-6f4e-7ab3-b1c4-5f6a7b8c9d0e",
"errors": [
{
"field": "email",
"code": "max_length",
"message": "email must be shorter than or equal to 75 characters"
}
],
"error": "trial_expired",
"upgrade_url": "https://app.skillsdb.com/settings/billing"
}{
"type": "https://help.skillsdb.com/api-docs/errors/validation-error",
"title": "Validation failed",
"status": 400,
"detail": "limit must be between 1 and 200",
"instance": "/api/v1/people",
"requestId": "0192c2f1-6f4e-7ab3-b1c4-5f6a7b8c9d0e",
"errors": [
{
"field": "email",
"code": "max_length",
"message": "email must be shorter than or equal to 75 characters"
}
],
"error": "trial_expired",
"upgrade_url": "https://app.skillsdb.com/settings/billing"
}{
"type": "https://help.skillsdb.com/api-docs/errors/validation-error",
"title": "Validation failed",
"status": 400,
"detail": "limit must be between 1 and 200",
"instance": "/api/v1/people",
"requestId": "0192c2f1-6f4e-7ab3-b1c4-5f6a7b8c9d0e",
"errors": [
{
"field": "email",
"code": "max_length",
"message": "email must be shorter than or equal to 75 characters"
}
],
"error": "trial_expired",
"upgrade_url": "https://app.skillsdb.com/settings/billing"
}{
"type": "https://help.skillsdb.com/api-docs/errors/validation-error",
"title": "Validation failed",
"status": 400,
"detail": "limit must be between 1 and 200",
"instance": "/api/v1/people",
"requestId": "0192c2f1-6f4e-7ab3-b1c4-5f6a7b8c9d0e",
"errors": [
{
"field": "email",
"code": "max_length",
"message": "email must be shorter than or equal to 75 characters"
}
],
"error": "trial_expired",
"upgrade_url": "https://app.skillsdb.com/settings/billing"
}{
"type": "https://help.skillsdb.com/api-docs/errors/validation-error",
"title": "Validation failed",
"status": 400,
"detail": "limit must be between 1 and 200",
"instance": "/api/v1/people",
"requestId": "0192c2f1-6f4e-7ab3-b1c4-5f6a7b8c9d0e",
"errors": [
{
"field": "email",
"code": "max_length",
"message": "email must be shorter than or equal to 75 characters"
}
],
"error": "trial_expired",
"upgrade_url": "https://app.skillsdb.com/settings/billing"
}Authorizations
An API key issued by a company administrator — an environment prefix (production: sdb_live_) followed by 32 characters. The prefix identifies the environment and MUST NOT be hard-coded or validated by clients; treat the whole key as opaque. Shown exactly once at creation; only a one-way hash is stored.
Scopes (granted per key on the create screen; :write never implies :read):
skills:readskills:writepeople:readpeople:writecareers:readcareers:writetraining:readtraining:writeflags:readflags:writeorg:readorg:writeassessments:readlearning:readlearning:writecertifications:readcertifications:writeassessments:writeproficiency:readproficiency:writesurveys:readsurveys:writelabels:readlabels:write
Headers
Any unique string (at most 200 characters). Retrying with the same key and identical content replays the stored response for 24h instead of executing twice.
200Body
500"Forklift Safety Refresher"
Learning type id (reference list). Required.
"1"
The file to upload after the call.
255"forklift-refresher.pdf"
4000"Annual refresher for licensed operators."
500"https://learn.example.com/forklift"
2.5
120
500"Per seat"
Training provider id (reference list).
"3"
Training format id (reference list).
"2"
Training audience id (reference list).
"1"
Training course level id (reference list).
"4"
100"LMS-4471"
Label ids to attach.
Skill ids to attach the item to.
Response
The created resource, enveloped.
Show child attributes
Show child attributes