--- title: "Certification and Retraining APIs: How-to guide" slug: "certification-and-retraining-apis-how-to-guide" description: "Learn how to create certifications, set expiration and recertification, enroll users, and manage awards and retraining with the Learning Plan APIs." tags: ["API", "Guide"] updated: 2026-11-11T12:43:02Z published: 2026-11-11T12:43:02Z canonical: "developer.docebo.com/certification-and-retraining-apis-how-to-guide" --- > ## Documentation Index > Fetch the complete documentation index at: https://developer.docebo.com/llms.txt > Use this file to discover all available pages before exploring further. # Certification and Retraining APIs: How-to guide **Purpose of this article**: a task-oriented, one-endpoint-at-a-time guide for working with certifications via the API. This article shows you *how* to call each endpoint. For a list of what changed for certifications on existing endpoints (blocked fields, new error codes, silent exclusions), see [Deprecated and changed API calls](/v1/docs/deprecated-and-changed-api-calls). **Availability**: Since the October 2026 primary release, the new type of certifications is generally available to all customers as a standard platform capability, and Launchpad does not offer an option to enable or disable it. Legacy certifications and the new type of certifications will co-exist for a while. > [!NOTE] > **Please note**: In this article, certifications always means the new type of certifications, which are Learning Plans with `"type": 2`. This article does not cover legacy certifications. ## Before you start All examples below assume: - Your platform domain is `https://yourdomain.docebosaas.com`. Replace it with your own. - You already have a valid OAuth2 access token (see [APIs authentication](/v1/docs/apis-authentication)) and send it as `Authorization: Bearer YOUR_ACCESS_TOKEN` on every call. - A certification is a learning plan with `"type": 2`. A standard Learning Plan is `"type": 1`. Everything below that is not certification-specific applies equally to both. - Required permission slugs are the same ones used for standard Learning Plans: See the *Permission model* note under each endpoint for which one applies. - `learning-plan/view` - `learning-plan/create` - `learning-plan/update` - `learning-plan/delete` - `enrollment/lp/manage` Power Users can work with the new type of certifications through the same Learning Plan permissions as standard Learning Plans. Grant the Power User the Learning Plan permissions for View, Create, Edit and Delete on their profile. Then assign the relevant resources to them, which can include the new type of certifications alongside standard Learning Plans. A Power User does not need access to the individual courses inside a certification to view or manage the certification itself, because access follows the Learning Plan permissions and the assignment of the certification to the Power User. The Power User needs the individual courses assigned only to add or select those courses in a certification, which is the same behavior as for standard Learning Plans. To try any of these calls interactively first, open your platform's API browser at `https://yourdomain.docebosaas.com/api-browser/`, select the **Learning Plan** service, and follow the steps in [Get started with the Docebo API browser](/v1/docs/get-started-with-the-docebo-api-browser) to authorize and use the **Try** button. Each endpoint section below tells you which grouping to open in the left-hand navigation. ## Create a learning plan or certification POST `/learningplan/v1/learningplans` **Permission required**: `learning-plan/create` (Superadmin; Power User within their perimeter). **What it does**: Creates a new Learning Plan or Certification. Set `"type": 2` to create a certification instead of a standard Learning Plan. ### Parameters (request body) | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `title` | string | Yes | Title of the Learning Plan or certification. | | `type` | integer | No (defaults to `1`) | `1` = standard Learning Plan, `2` = Certification. | | `description` | string | Yes | Description text. | > [!NOTE] > **Please note**: This endpoint does not accept the `certification_config` object. To set the expiration and renewal behavior of a certification, create the certification first, and then update it with the update endpoint described in section 4. If your platform has mandatory additional fields, you must also pass them in an `additional_fields` array of objects, each with an `id` and a `value`. Without them, the call returns error `1001` (Missing parameter: additional_field ids). ### Example request body ```json {  "title": "Certified Safety Trainer",  "type": 2,  "description": "Annual safety certification for warehouse staff." } ``` ### Example calls **API browser** - Go to: - Service **Learning Plan** - Grouping **Learning Plans** - POST `/learningplan/v1/learningplans` - Paste the body above into the request body field - Click **Try** **JavaScript (Fetch)** ```javascript const response = await fetch('https://yourdomain.docebosaas.com/learningplan/v1/learningplans', {  method: 'POST',  headers: {    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',    'Content-Type': 'application/json'  },  body: JSON.stringify({    title: 'Certified Safety Trainer',    type: 2,    description: 'Annual safety certification for warehouse staff.'  }) }); const data = await response.json(); console.log(data); ``` **Python (requests)** ```python import requests url = "https://yourdomain.docebosaas.com/learningplan/v1/learningplans" headers = {    "Authorization": "Bearer YOUR_ACCESS_TOKEN",    "Content-Type": "application/json" } payload = {    "title": "Certified Safety Trainer",    "type": 2,    "description": "Annual safety certification for warehouse staff." } response = requests.post(url, headers=headers, json=payload) print(response.json()) ``` **PHP (cURL)** ```php "https://yourdomain.docebosaas.com/learningplan/v1/learningplans",    CURLOPT_RETURNTRANSFER => true,    CURLOPT_CUSTOMREQUEST => "POST",    CURLOPT_POSTFIELDS => json_encode([        "title" => "Certified Safety Trainer",        "type" => 2,        "description" => "Annual safety certification for warehouse staff."    ]),    CURLOPT_HTTPHEADER => [        "Authorization: Bearer YOUR_ACCESS_TOKEN",        "Content-Type: application/json"    ], ]); $response = curl_exec($curl); curl_close($curl); echo $response; ``` **Postman** - Method: POST - URL: `https://yourdomain.docebosaas.com/learningplan/v1/learningplans` - Headers: `Authorization: Bearer YOUR_ACCESS_TOKEN`, `Content-Type: application/json` - Body (raw, JSON): same as the example request body above ### Response Returns `data.id_lp`, the ID of the new Learning Plan or certification. ## Retrieve a learning plan or certification GET `/learningplan/v1/learningplans/{lp_id}` **Permission required**: `learning-plan/view`. **What it does**: Returns all properties of a single Learning Plan or Certification. For certifications, the response includes the `certification_config` object. ### Parameters | Parameter | Location | Type | Required | Description | | --- | --- | --- | --- | --- | | `lp_id` | path | integer | Yes | The ID of the Learning Plan or certification. | ### Example calls **API browser** - Go to: - Service **Learning Plan** - Grouping **Learning Plans** - GET `/learningplan/v1/learningplans/{lp_id}` - Enter the `lp_id` - Click **Try** **JavaScript (Fetch)** ```javascript const lpId = 123; const response = await fetch(`https://yourdomain.docebosaas.com/learningplan/v1/learningplans/${lpId}`, {  headers: { 'Authorization': 'Bearer YOUR_ACCESS_TOKEN' } }); const data = await response.json(); console.log(data); ``` **Python (requests)** ```python import requests lp_id = 123 url = f"https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{lp_id}" headers = {"Authorization": "Bearer YOUR_ACCESS_TOKEN"} response = requests.get(url, headers=headers) print(response.json()) ``` **PHP (cURL)** ```php "https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{$lpId}",    CURLOPT_RETURNTRANSFER => true,    CURLOPT_HTTPHEADER => ["Authorization: Bearer YOUR_ACCESS_TOKEN"], ]); $response = curl_exec($curl); curl_close($curl); echo $response; ``` **Postman** - Method: GET - URL: `https://yourdomain.docebosaas.com/learningplan/v1/learningplans/123` - Headers: `Authorization: Bearer YOUR_ACCESS_TOKEN` ### Response Returns the Learning Plan/certification object. For `type: 2`, includes `certification_config`. ## List learning plans and certifications GET `/learningplan/v1/learningplans` **Permission required**: `learning-plan/view` (filtering by `type_filter=2` is available to Superadmins only). **What it does**: Returns a paginated list of Learning Plans and/or Certifications. ### Parameters (query string) | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `type_filter` | integer | No | `1` for standard Learning Plans only, `2` for Certifications only. Omit for both. | | `award_status` | string | No | Certification-only filter on award status. Accepted values are `valid` (at least one valid award), `expiring` (at least one expiring award) and `expired` (at least one expired award). You can pass more than one value. | | `page` | integer | No | Page number, default `1`. | | `page_size` | integer | No | Items per page (1–200), default `10`. | | `sort_attr` | string | No | Field to sort by. Possible values are: - `uuid` - `code` - `title` - `price` - `credits` - `created_on` - `updated_on` Default value: `created_on` | | `sort_dir` | string | No | - `asc` - `desc` Default value: `desc` | ### Example calls **API browser** - Go to: - Service **Learning Plan** - Grouping **Learning Plans** - GET `/learningplan/v1/learningplans` - Set `type_filter=2` in the parameters panel - Click **Try** **JavaScript (Fetch)** ```javascript const response = await fetch('https://yourdomain.docebosaas.com/learningplan/v1/learningplans?type_filter=2&page_size=20', {  headers: { 'Authorization': 'Bearer YOUR_ACCESS_TOKEN' } }); const data = await response.json(); console.log(data); ``` **Python (requests)** ```python import requests url = "https://yourdomain.docebosaas.com/learningplan/v1/learningplans" headers = {"Authorization": "Bearer YOUR_ACCESS_TOKEN"} params = {"type_filter": 2, "page_size": 20} response = requests.get(url, headers=headers, params=params) print(response.json()) ``` **PHP (cURL)** ```php "https://yourdomain.docebosaas.com/learningplan/v1/learningplans?type_filter=2&page_size=20",    CURLOPT_RETURNTRANSFER => true,    CURLOPT_HTTPHEADER => ["Authorization: Bearer YOUR_ACCESS_TOKEN"], ]); $response = curl_exec($curl); curl_close($curl); echo $response; ``` **Postman** - Method: GET - URL: `https://yourdomain.docebosaas.com/learningplan/v1/learningplans?type_filter=2&page_size=20` - Headers: `Authorization: Bearer YOUR_ACCESS_TOKEN` ### Response Returns a paginated `items` array. For certification results, each item includes `award_counters` (lazy-loaded certification award summary). ## Update a learning plan or certification PUT `/learningplan/v1/learningplans/{lp_id}` **Permission required**: `learning-plan/update`. **What it does**: Updates a Learning Plan or Certification, including its `certification_config`. > [!NOTE] > **Please note**: Updating a certification's configuration does **not** trigger the `LearningPlanUpdated` webhook event. If your integration listens for that event to detect changes, you will not be notified of certification updates today. See [Deprecated and changed API calls](/v1/docs/deprecated-and-changed-api-calls) for details. ### Parameters (request body) | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `title` | string | Yes | Updated title. | | `description` | string | Yes | Updated description. | | `lang_code` | string | Yes | The language of the learning plan or certification, written as the language name, such as `english`. | | `certification_config` | object | No (certifications only) | Certification-specific settings (expiration and renewal behavior). Set these settings with this endpoint, because the create endpoint does not accept this object. The next table describes the structure of this object. | ### The certification_config object Use the `certification_config` object to set when awards of a certification expire and how learners renew them. The following table lists each field with its full path inside the object. | Field | Type | Description | | --- | --- | --- | | `expiration` | object | Required. Defines when an award expires. | | `expiration.type` | string | Required. The expiration logic. Possible values are: - `NONE` - `FIXED_DATE` - `RELATIVE` | | `expiration.fixed_date.month` | integer | Required when `type` is `FIXED_DATE`. The month, from 1 to 12. | | `expiration.fixed_date.day` | integer | Required when `type` is `FIXED_DATE`. The day of the month, from 1 to 31. | | `expiration.relative.duration` | integer | Required when `type` is `RELATIVE`. The duration value. The value must be at least 1. | | `expiration.relative.unit` | string | Required when `type` is `RELATIVE`. The time unit of the duration. Possible values are: - `DAY` - `WEEK` - `MONTH` - `YEAR` | | `expiration.relative.snap_to_end` | string | Required when `type` is `RELATIVE`. Whether the date snaps to the end of a period. Possible values are: - `NONE` - `MONTH` - `YEAR` | | `recert` | object | Required. Defines how learners renew an award. Pass this object next to `expiration`, not inside it. Always include it when you pass `certification_config`. For `ANYTIME` renewal, you do not need to pass `window`. | | `recert.type` | string | Required. The renewal logic. Possible values are: - `ANYTIME` - `WINDOW` | | `recert.window.start.duration` | integer | Required. The duration value for the start of the renewal window. The value must be at least 1. | | `recert.window.start.unit` | string | Required. The time unit. The only possible value is `DAY`. | | `recert.window.end.type` | string | Required. The logic for the end of the renewal window. Possible values are: - `NONE` - `AT_EXPIRATION` - `RELATIVE` > [!TIP] > Tip: > > The values `AT_EXPIRATION` and `RELATIVE` apply only when a learner renews an existing certification. | | `recert.window.end.relative.duration` | integer | Required. The duration value for the end of the renewal window. The value must be at least 1. | | `recert.window.end.relative.unit` | string | Required. The time unit. The only possible value is `DAY`. | ### Example request body ```json {  "title": "Certified Safety Trainer",  "description": "Annual safety certification for warehouse staff.",  "lang_code": "english",  "certification_config": {    "expiration": {      "type": "RELATIVE",      "relative": {        "duration": 1,        "unit": "YEAR",        "snap_to_end": "NONE"      }    },    "recert": {      "type": "WINDOW",      "window": {        "start": {          "duration": 30,          "unit": "DAY"        },        "end": {          "type": "RELATIVE",          "relative": {            "duration": 7,            "unit": "DAY"          }        }      }    }  } } ``` ### Example calls **API browser** - Go to: - Service **Learning Plan** - Grouping **Learning Plans** - PUT `/learningplan/v1/learningplans/{lp_id}` - Enter `lp_id` - Edit the body - Click **Try** **JavaScript (Fetch)** ```javascript const lpId = 123; const response = await fetch(`https://yourdomain.docebosaas.com/learningplan/v1/learningplans/${lpId}`, {  method: 'PUT',  headers: {    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',    'Content-Type': 'application/json'  },  body: JSON.stringify({    title: 'Certified Safety Trainer',    description: 'Annual safety certification for warehouse staff.',    lang_code: 'english',    certification_config: {      expiration: {        type: 'RELATIVE',        relative: { duration: 1, unit: 'YEAR', snap_to_end: 'NONE' }      },      recert: {        type: 'WINDOW',        window: {          start: { duration: 30, unit: 'DAY' },          end: { type: 'RELATIVE', relative: { duration: 7, unit: 'DAY' } }        }      }    }  }) }); const data = await response.json(); console.log(data); ``` **Python (requests)** ```python import requests lp_id = 123 url = f"https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{lp_id}" headers = {    "Authorization": "Bearer YOUR_ACCESS_TOKEN",    "Content-Type": "application/json" } payload = {    "title": "Certified Safety Trainer",    "description": "Annual safety certification for warehouse staff.",    "lang_code": "english",    "certification_config": {        "expiration": {            "type": "RELATIVE",            "relative": {"duration": 1, "unit": "YEAR", "snap_to_end": "NONE"}        },        "recert": {            "type": "WINDOW",            "window": {                "start": {"duration": 30, "unit": "DAY"},                "end": {"type": "RELATIVE", "relative": {"duration": 7, "unit": "DAY"}}            }        }    } } response = requests.put(url, headers=headers, json=payload) print(response.json()) ``` **PHP (cURL)** ```php "https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{$lpId}",    CURLOPT_RETURNTRANSFER => true,    CURLOPT_CUSTOMREQUEST => "PUT",    CURLOPT_POSTFIELDS => json_encode([        "title" => "Certified Safety Trainer",        "description" => "Annual safety certification for warehouse staff.",        "lang_code" => "english",        "certification_config" => [            "expiration" => [                "type" => "RELATIVE",                "relative" => ["duration" => 1, "unit" => "YEAR", "snap_to_end" => "NONE"]            ],            "recert" => [                "type" => "WINDOW",                "window" => [                    "start" => ["duration" => 30, "unit" => "DAY"],                    "end" => ["type" => "RELATIVE", "relative" => ["duration" => 7, "unit" => "DAY"]]                ]            ]        ]    ]),    CURLOPT_HTTPHEADER => [        "Authorization: Bearer YOUR_ACCESS_TOKEN",        "Content-Type: application/json"    ], ]); $response = curl_exec($curl); curl_close($curl); echo $response; ``` **Postman** - Method: PUT - URL: `https://yourdomain.docebosaas.com/learningplan/v1/learningplans/123` - Headers: `Authorization: Bearer YOUR_ACCESS_TOKEN`, `Content-Type: application/json` - Body (raw, JSON): same as the example request body above ### Response Returns the updated object. ## Delete learning plans or certifications DELETE `/learningplan/v1/learningplans` **Permission required**: `learning-plan/delete`. **What it does**: Deletes one or more Learning Plans or Certifications by ID. ### Parameters (request body) | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of integers | Yes | The Learning Plan / certification IDs to delete. | ### Example calls **API browser** - Go to: - Service **Learning Plan** - Grouping **Learning Plans** - DELETE `/learningplan/v1/learningplans` - Enter the `items` array in the body - Click **Try** **JavaScript (Fetch)** ```javascript const response = await fetch('https://yourdomain.docebosaas.com/learningplan/v1/learningplans', {  method: 'DELETE',  headers: {    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',    'Content-Type': 'application/json'  },  body: JSON.stringify({ items: [123, 124] }) }); const data = await response.json(); console.log(data); ``` **Python (requests)** ```python import requests url = "https://yourdomain.docebosaas.com/learningplan/v1/learningplans" headers = {    "Authorization": "Bearer YOUR_ACCESS_TOKEN",    "Content-Type": "application/json" } payload = {"items": [123, 124]} response = requests.delete(url, headers=headers, json=payload) print(response.json()) ``` **PHP (cURL)** ```php "https://yourdomain.docebosaas.com/learningplan/v1/learningplans",    CURLOPT_RETURNTRANSFER => true,    CURLOPT_CUSTOMREQUEST => "DELETE",    CURLOPT_POSTFIELDS => json_encode(["items" => [123, 124]]),    CURLOPT_HTTPHEADER => [        "Authorization: Bearer YOUR_ACCESS_TOKEN",        "Content-Type: application/json"    ], ]); $response = curl_exec($curl); curl_close($curl); echo $response; ``` **Postman** - Method: DELETE - URL: `https://yourdomain.docebosaas.com/learningplan/v1/learningplans` - Headers: `Authorization: Bearer YOUR_ACCESS_TOKEN`, `Content-Type: application/json` - Body (raw, JSON): `{"items": [123, 124]}` ### Response Returns one result for each ID. Each result contains: - `row_index` - `success` - `output` (where `output` contains the `id` ) ## Update the status of multiple learning plans or certifications PUT `/learningplan/v1/learningplans/statuses` **What it does**: Sets the status of multiple Learning Plans or Certifications in a single call. The status controls whether an item is published or under maintenance. ### Parameters (request body) | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of objects | Yes | The items to update. Each object contains an `id` (the ID of the Learning Plan or certification) and a `status`. Use `1` for published and `0` for under maintenance. Both values are required. | ### Example calls **API browser** - Go to: - Service **Learning Plan** - Grouping **Learning Plans** - PUT `/learningplan/v1/learningplans/statuses` - Enter the `items` array in the body - Click **Try** **JavaScript (Fetch)** ```javascript const response = await fetch('https://yourdomain.docebosaas.com/learningplan/v1/learningplans/statuses', {  method: 'PUT',  headers: {    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',    'Content-Type': 'application/json'  },  body: JSON.stringify({ items: [{ id: 123, status: 1 }, { id: 124, status: 0 }] }) }); const data = await response.json(); console.log(data); ``` **Python (requests)** ```python import requests url = "https://yourdomain.docebosaas.com/learningplan/v1/learningplans/statuses" headers = {    "Authorization": "Bearer YOUR_ACCESS_TOKEN",    "Content-Type": "application/json" } payload = {"items": [{"id": 123, "status": 1}, {"id": 124, "status": 0}]} response = requests.put(url, headers=headers, json=payload) print(response.json()) ``` **PHP (cURL)** ```php "https://yourdomain.docebosaas.com/learningplan/v1/learningplans/statuses",    CURLOPT_RETURNTRANSFER => true,    CURLOPT_CUSTOMREQUEST => "PUT",    CURLOPT_POSTFIELDS => json_encode(["items" => [["id" => 123, "status" => 1], ["id" => 124, "status" => 0]]]),    CURLOPT_HTTPHEADER => [        "Authorization: Bearer YOUR_ACCESS_TOKEN",        "Content-Type: application/json"    ], ]); $response = curl_exec($curl); curl_close($curl); echo $response; ``` **Postman** - Method: PUT - URL: `https://yourdomain.docebosaas.com/learningplan/v1/learningplans/statuses` - Headers: `Authorization: Bearer YOUR_ACCESS_TOKEN`, `Content-Type: application/json` - Body (raw, JSON): `{"items": [{"id": 123, "status": 1}, {"id": 124, "status": 0}]}` ### Response The response contains: - `success` (`true` if at least one update completed) - `updated_ids` - `not_updated_ids` - `not_found_ids` ## List enrollments for a learning plan or certification GET `/learningplan/v1/learningplans/{lp_id}/enrollments` **Permission required**: `learning-plan/view` + `enrollment/lp/manage`. **What it does**: Lists the users enrolled in a specific Learning Plan or Certification. > [!TIP] > **Tip**: For certification enrollments, the response also includes a `type` field, and completed enrollments include an `award` sub-object plus a `previous_award` count (for renewed certifications). ### Parameters | Parameter | Location | Type | Required | Description | | --- | --- | --- | --- | --- | | `lp_id` | path | integer | Yes | The Learning Plan / certification ID. | | `page` | query | integer | No | Page number. | | `page_size` | query | integer | No | Items per page (1–200). | ### Example calls **API browser** - Go to: - Service **Learning Plan** - Grouping **Enrollments** - GET `/learningplan/v1/learningplans/{lp_id}/enrollments` - Enter `lp_id` - Click **Try** **JavaScript (Fetch)** ```javascript const lpId = 123; const response = await fetch(`https://yourdomain.docebosaas.com/learningplan/v1/learningplans/${lpId}/enrollments`, {  headers: { 'Authorization': 'Bearer YOUR_ACCESS_TOKEN' } }); const data = await response.json(); console.log(data); ``` **Python (requests)** ```python import requests lp_id = 123 url = f"https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{lp_id}/enrollments" headers = {"Authorization": "Bearer YOUR_ACCESS_TOKEN"} response = requests.get(url, headers=headers) print(response.json()) ``` **PHP (cURL)** ```php "https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{$lpId}/enrollments",    CURLOPT_RETURNTRANSFER => true,    CURLOPT_HTTPHEADER => ["Authorization: Bearer YOUR_ACCESS_TOKEN"], ]); $response = curl_exec($curl); curl_close($curl); echo $response; ``` **Postman** - Method: GET - URL: `https://yourdomain.docebosaas.com/learningplan/v1/learningplans/123/enrollments` - Headers: `Authorization: Bearer YOUR_ACCESS_TOKEN` ### Response Returns an `items` array of enrollments. For certifications, completed items include: - `award` - `previous_award` --- ## Enroll a user in a learning plan or certification POST `/learningplan/v1/learningplans/{lp_id}/enrollments/{user_id}` **Permission required**: `enrollment/lp/manage`. **What it does**: Enrolls a single user in a Learning Plan or Certification. > [!NOTE] > **Please note**: `status: "completed"` is accepted for standard Learning Plans but **rejected for certifications**. See [Deprecated and changed API calls](/v1/docs/deprecated-and-changed-api-calls). ### Parameters | Parameter | Location | Type | Required | Description | | --- | --- | --- | --- | --- | | `lp_id` | path | integer | Yes | The Learning Plan / certification ID. | | `user_id` | path | integer | Yes | The user to enroll. | | `status` | body | string | No | Initial enrollment status. Not `completed` for certifications. If you pass `completed` for a certification, the call returns a 404 error with code `1014`, Learning plan not found. | | `assignment_type` | body | string | No | The assignment type of the enrollment. You can pass this value only when the attribute is enabled in the platform advanced settings. Possible values are: - `mandatory` - `required` - `recommended` - `optional` | | `validity_start_at` | body | string | No | The start of the enrollment validity, in UTC and in the format `YYYY-MM-DD hh:mm:ss`. | | `validity_end_at` | body | string | No | The end of the enrollment validity, in UTC and in the format `YYYY-MM-DD hh:mm:ss`. | ### Example calls **API browser** - Go to: - Service **Learning Plan** - Grouping **Enrollments** - POST `/learningplan/v1/learningplans/{lp_id}/enrollments/{user_id}` - Enter `lp_id` and `user_id` - Click **Try** **JavaScript (Fetch)** ```javascript const lpId = 123, userId = 456; const response = await fetch(`https://yourdomain.docebosaas.com/learningplan/v1/learningplans/${lpId}/enrollments/${userId}`, {  method: 'POST',  headers: {    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',    'Content-Type': 'application/json'  },  body: JSON.stringify({}) }); const data = await response.json(); console.log(data); ``` **Python (requests)** ```python import requests lp_id, user_id = 123, 456 url = f"https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{lp_id}/enrollments/{user_id}" headers = {    "Authorization": "Bearer YOUR_ACCESS_TOKEN",    "Content-Type": "application/json" } response = requests.post(url, headers=headers, json={}) print(response.json()) ``` **PHP (cURL)** ```php "https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{$lpId}/enrollments/{$userId}",    CURLOPT_RETURNTRANSFER => true,    CURLOPT_CUSTOMREQUEST => "POST",    CURLOPT_POSTFIELDS => json_encode([]),    CURLOPT_HTTPHEADER => [        "Authorization: Bearer YOUR_ACCESS_TOKEN",        "Content-Type: application/json"    ], ]); $response = curl_exec($curl); curl_close($curl); echo $response; ``` **Postman** - Method: POST - URL: `https://yourdomain.docebosaas.com/learningplan/v1/learningplans/123/enrollments/456` - Headers: `Authorization: Bearer YOUR_ACCESS_TOKEN`, `Content-Type: application/json` - Body (raw, JSON): `{}` ### Response Returns the created enrollment. ## Update a user's enrollment PATCH `/learningplan/v1/learningplans/{lp_id}/enrollments/{user_id}` **Permission required**: `enrollment/lp/manage`. **What it does**: Updates a user's enrollment in a Learning Plan or Certification. > [!NOTE] > **Please note**: `validity_dates_reset` and `status: "completed"` are accepted for standard Learning Plans but **both rejected for certifications** (error `1016`). A certification is completed only through the award operation, which creates the award and completes the enrollment together, so this call does not allow `status: "completed"`. The validity dates of a certification follow the days of validity configured on the certification when the learner first accesses it, and on every renewal they come from the renewal window of the award, so this call does not allow `validity_dates_reset` either. See [Deprecated and changed API calls](/v1/docs/deprecated-and-changed-api-calls). **To mark a user as having completed a certification**, issue the award instead: - [Single user](/v1/docs/certification-and-retraining-apis-how-to-guide#14-issue-an-award-to-a-user): POST `/learningplan/v1/learningplans/{lp_id}/users/{user_id}/award`. The body requires `completion_date`, formatted as `YYYY-MM-DD HH:mm:ss` in UTC. The date cannot be in the future. - [Multiple users](/v1/docs/certification-and-retraining-apis-how-to-guide#15-issue-awards-in-bulk): POST `/learningplan/v1/learningplans/{lp_id}/award/bulk`. The body requires `items`, a list of objects that each contain a `user_id` (between 1 and 2,000 items), and `options.completion_date`. The response is either synchronous with per-user results or asynchronous with a `job_descriptor`. Both endpoints work only on learning plans of type certification. ### Parameters | Parameter | Location | Type | Required | Description | | --- | --- | --- | --- | --- | | `lp_id` | path | integer | Yes | The Learning Plan / certification ID. | | `user_id` | path | integer | Yes | The enrolled user. | | `validity_dates_reset` | body | boolean | No | Not accepted for certifications. | | `validity_start_at` | body | string | No | The start of the enrollment validity, in UTC and in the format `YYYY-MM-DD hh:mm:ss`. Pass it together with `validity_end_at`. | | `validity_end_at` | body | string | No | The end of the enrollment validity, in UTC and in the format `YYYY-MM-DD hh:mm:ss`. Pass it together with `validity_start_at`. If you pass only one of the two, the call returns error `1010`. | | `enrolled_at` | body | string | No | The enrollment date. | | `propagate_to_courses` | body | boolean | No | If `true`, the new enrollment date also applies to the courses. This works only when you pass `enrolled_at`. | | `assignment_type` | body | string | No | The assignment type of the enrollment. Possible values are `mandatory`, `required`, `recommended` and `optional`. | | `status` | body | string | No | Not `completed` for certifications. | ### Example calls **API browser** - Go to: - Service **Learning Plan** - Grouping **Enrollments** - PATCH `/learningplans/{id}/enrollments/{user_id}` - Enter `lp_id`/`user_id` - Click **Try** **JavaScript (Fetch)** ```javascript const lpId = 123, userId = 456; const response = await fetch(`https://yourdomain.docebosaas.com/learningplan/v1/learningplans/${lpId}/enrollments/${userId}`, {  method: 'PATCH',  headers: {    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',    'Content-Type': 'application/json'  },  body: JSON.stringify({}) }); const data = await response.json(); console.log(data); ``` **Python (requests)** ```python import requests lp_id, user_id = 123, 456 url = f"https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{lp_id}/enrollments/{user_id}" headers = {    "Authorization": "Bearer YOUR_ACCESS_TOKEN",    "Content-Type": "application/json" } response = requests.patch(url, headers=headers, json={}) print(response.json()) ``` **PHP (cURL)** ```php "https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{$lpId}/enrollments/{$userId}",    CURLOPT_RETURNTRANSFER => true,    CURLOPT_CUSTOMREQUEST => "PATCH",    CURLOPT_POSTFIELDS => json_encode([]),    CURLOPT_HTTPHEADER => [        "Authorization: Bearer YOUR_ACCESS_TOKEN",        "Content-Type: application/json"    ], ]); $response = curl_exec($curl); curl_close($curl); echo $response; ``` **Postman** - Method: PATCH - URL: `https://yourdomain.docebosaas.com/learningplan/v1/learningplans/123/enrollments/456` - Headers: `Authorization: Bearer YOUR_ACCESS_TOKEN`, `Content-Type: application/json` - Body (raw, JSON): `{}` ### Response Returns the updated enrollment. ## Remove a user's enrollment DELETE `/learningplan/v1/learningplans/{lp_id}/enrollments/{user_id}` **Permission required**: `enrollment/lp/manage`. **What it does**: Removes a user's enrollment from a Learning Plan or Certification. > [!NOTE] > **Please note**: For certifications, this removes the enrollment but does **not** revoke an already-issued certification award. ### Parameters | Parameter | Location | Type | Required | Description | | --- | --- | --- | --- | --- | | `lp_id` | path | integer | Yes | The Learning Plan / certification ID. | | `user_id` | path | integer | Yes | The enrolled user. | | `unenroll_from_courses` | body | boolean | No | If `true`, the user is also unenrolled from the courses of the Learning Plan. The user stays enrolled in courses that belong to other Learning Plan enrollments. | | `keep_in_progress` | body | boolean | No | If `true`, the user is not unenrolled from courses that are in progress. This works only when `unenroll_from_courses` is `true`. | | `keep_completed` | body | boolean | No | If `true`, the user is not unenrolled from courses that are completed. This works only when `unenroll_from_courses` is `true`. | ### Example calls **API browser** - Go to: - Service **Learning Plan** - Grouping **Enrollments** - DELETE `/learningplans/{id}/enrollments/{user_id}` - Enter `lp_id`/`user_id` - Click **Try** **JavaScript (Fetch)** ```javascript const lpId = 123, userId = 456; const response = await fetch(`https://yourdomain.docebosaas.com/learningplan/v1/learningplans/${lpId}/enrollments/${userId}`, {  method: 'DELETE',  headers: { 'Authorization': 'Bearer YOUR_ACCESS_TOKEN' } }); const data = await response.json(); console.log(data); ``` **Python (requests)** ```python import requests lp_id, user_id = 123, 456 url = f"https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{lp_id}/enrollments/{user_id}" headers = {"Authorization": "Bearer YOUR_ACCESS_TOKEN"} response = requests.delete(url, headers=headers) print(response.json()) ``` **PHP (cURL)** ```php "https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{$lpId}/enrollments/{$userId}",    CURLOPT_RETURNTRANSFER => true,    CURLOPT_CUSTOMREQUEST => "DELETE",    CURLOPT_HTTPHEADER => ["Authorization: Bearer YOUR_ACCESS_TOKEN"], ]); $response = curl_exec($curl); curl_close($curl); echo $response; ``` **Postman** - Method: DELETE - URL: `https://yourdomain.docebosaas.com/learningplan/v1/learningplans/123/enrollments/456` - Headers: `Authorization: Bearer YOUR_ACCESS_TOKEN` ### Response Returns a confirmation of removal. Does not affect any previously issued award record. ## Bulk enroll users POST `/learningplan/v1/learningplans/{lp_id}/enrollments/bulk` **Permission required**: `enrollment/lp/manage`. **What it does**: Enrolls multiple users in a Learning Plan or Certification in one call. > [!NOTE] > **Please note**: `options.status: "completed"` is rejected for certifications, same as the single-user enroll call. See [Deprecated and changed API calls](/v1/docs/deprecated-and-changed-api-calls). ### Parameters (request body) | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `items` | object | Yes | The users to enroll. This object contains: - `user_ids` (a list of user IDs) - `group_ids` (a list of group IDs) - `branches` (a list of branches) Pass at least one of them. | | `options` | object | No | Enrollment options. `status: "completed"` is not accepted for certifications. | ### Example calls **API browser** - Go to: - Service **Learning Plan** - Grouping **Enrollments** - POST `/learningplans/{id}/enrollments/bulk` - Enter `lp_id` and body - Click **Try** **JavaScript (Fetch)** ```javascript const lpId = 123; const response = await fetch(`https://yourdomain.docebosaas.com/learningplan/v1/learningplans/${lpId}/enrollments/bulk`, {  method: 'POST',  headers: {    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',    'Content-Type': 'application/json'  },  body: JSON.stringify({ items: { user_ids: [456, 457, 458] } }) }); const data = await response.json(); console.log(data); ``` **Python (requests)** ```python import requests lp_id = 123 url = f"https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{lp_id}/enrollments/bulk" headers = {    "Authorization": "Bearer YOUR_ACCESS_TOKEN",    "Content-Type": "application/json" } payload = {"items": {"user_ids": [456, 457, 458]}} response = requests.post(url, headers=headers, json=payload) print(response.json()) ``` **PHP (cURL)** ```php "https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{$lpId}/enrollments/bulk",    CURLOPT_RETURNTRANSFER => true,    CURLOPT_CUSTOMREQUEST => "POST",    CURLOPT_POSTFIELDS => json_encode(["items" => ["user_ids" => [456, 457, 458]]]),    CURLOPT_HTTPHEADER => [        "Authorization: Bearer YOUR_ACCESS_TOKEN",        "Content-Type: application/json"    ], ]); $response = curl_exec($curl); curl_close($curl); echo $response; ``` **Postman** - Method: POST - URL: `https://yourdomain.docebosaas.com/learningplan/v1/learningplans/123/enrollments/bulk` - Headers: `Authorization: Bearer YOUR_ACCESS_TOKEN`, `Content-Type: application/json` - Body (raw, JSON): `{"items": {"user_ids": [456, 457, 458]}}` ### Response For a single user, processed synchronously. For more than one user, this is processed as a background job. Check the job status endpoint referenced in [Overview of API services and endpoints](/v1/docs/overview-of-api-services-and-endpoints). ## Bulk update enrollments PATCH `/learningplan/v1/learningplans/{lp_id}/enrollments/bulk` **Permission required**: `enrollment/lp/manage`. **What it does**: Updates multiple users' enrollments in a Learning Plan or Certification in one call. > [!NOTE] > **Please note**: `options.validity_dates_reset` and `options.status: "completed"` are both rejected for certifications. See [Deprecated and changed API calls](/v1/docs/deprecated-and-changed-api-calls). ### Parameters (request body) | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of objects | Yes | The enrollments to update. Each object contains the `id` of a user. Instead of `items`, you can pass `items_by_properties` to select enrollments with filters. Pass one of the two, **not both**. | | `options` | object | Yes | Update options. You must pass at least one option field. Pass `validity_start_at` and `validity_end_at` together, because the call returns error `1010` if you pass only one of them. `validity_dates_reset` and `status: "completed"` not accepted for certifications. | ### Example calls **API browser** - Go to: - Service **Learning Plan** - Grouping **Enrollments** - PATCH `/learningplans/{id}/enrollments/bulk` - Enter `lp_id` and body - Click **Try** **JavaScript (Fetch)** ```javascript const lpId = 123; const response = await fetch(`https://yourdomain.docebosaas.com/learningplan/v1/learningplans/${lpId}/enrollments/bulk`, {  method: 'PATCH',  headers: {    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',    'Content-Type': 'application/json'  },  body: JSON.stringify({ items: [{ id: 456 }, { id: 457 }], options: { validity_start_at: '2026-11-01 00:00:00', validity_end_at: '2027-12-31 23:59:59' } }) }); const data = await response.json(); console.log(data); ``` **Python (requests)** ```python import requests lp_id = 123 url = f"https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{lp_id}/enrollments/bulk" headers = {    "Authorization": "Bearer YOUR_ACCESS_TOKEN",    "Content-Type": "application/json" } payload = {"items": [{"id": 456}, {"id": 457}], "options": {"validity_start_at": "2026-11-01 00:00:00", "validity_end_at": "2027-12-31 23:59:59"}} response = requests.patch(url, headers=headers, json=payload) print(response.json()) ``` **PHP (cURL)** ```php "https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{$lpId}/enrollments/bulk",    CURLOPT_RETURNTRANSFER => true,    CURLOPT_CUSTOMREQUEST => "PATCH",    CURLOPT_POSTFIELDS => json_encode(["items" => [["id" => 456], ["id" => 457]], "options" => ["validity_start_at" => "2026-11-01 00:00:00", "validity_end_at" => "2027-12-31 23:59:59"]]),    CURLOPT_HTTPHEADER => [        "Authorization: Bearer YOUR_ACCESS_TOKEN",        "Content-Type: application/json"    ], ]); $response = curl_exec($curl); curl_close($curl); echo $response; ``` **Postman** - Method: PATCH - URL: `https://yourdomain.docebosaas.com/learningplan/v1/learningplans/123/enrollments/bulk` - Headers: `Authorization: Bearer YOUR_ACCESS_TOKEN`, `Content-Type: application/json` - Body (raw, JSON): `{"items": [{"id": 456}, {"id": 457}], "options": {"validity_start_at": "2026-11-01 00:00:00", "validity_end_at": "2027-12-31 23:59:59"}}` ### Response Processed as a background job for more than one user. Check job status as noted. ## Bulk remove enrollments DELETE `/learningplan/v1/learningplans/{lp_id}/enrollments/bulk` **Permission required**: `enrollment/lp/manage`. **What it does**: Removes multiple users' enrollments from a Learning Plan or Certification in one call. > [!NOTE] > **Please note**: Same as the single-user removal: for certifications, this does not revoke previously issued awards. ### Parameters (request body) | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `items` | array of objects | Yes | The enrollments to remove. Each object contains the `id` of a user. Instead of `items`, you can pass `items_by_properties` to select enrollments with filters. Pass one of the two, not both. | ### Example calls **API browser** - Go to: - Service **Learning Plan** - Grouping **Enrollments** - DELETE `/learningplans/{id}/enrollments/bulk` - Enter `lp_id` and body - Click **Try** **JavaScript (Fetch)** ```javascript const lpId = 123; const response = await fetch(`https://yourdomain.docebosaas.com/learningplan/v1/learningplans/${lpId}/enrollments/bulk`, {  method: 'DELETE',  headers: {    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',    'Content-Type': 'application/json'  },  body: JSON.stringify({ items: [{ id: 456 }, { id: 457 }] }) }); const data = await response.json(); console.log(data); ``` **Python (requests)** ```python import requests lp_id = 123 url = f"https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{lp_id}/enrollments/bulk" headers = {    "Authorization": "Bearer YOUR_ACCESS_TOKEN",    "Content-Type": "application/json" } payload = {"items": [{"id": 456}, {"id": 457}]} response = requests.delete(url, headers=headers, json=payload) print(response.json()) ``` **PHP (cURL)** ```php "https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{$lpId}/enrollments/bulk",    CURLOPT_RETURNTRANSFER => true,    CURLOPT_CUSTOMREQUEST => "DELETE",    CURLOPT_POSTFIELDS => json_encode(["items" => [["id" => 456], ["id" => 457]]]),    CURLOPT_HTTPHEADER => [        "Authorization: Bearer YOUR_ACCESS_TOKEN",        "Content-Type: application/json"    ], ]); $response = curl_exec($curl); curl_close($curl); echo $response; ``` **Postman** - Method: DELETE - URL: `https://yourdomain.docebosaas.com/learningplan/v1/learningplans/123/enrollments/bulk` - Headers: `Authorization: Bearer YOUR_ACCESS_TOKEN`, `Content-Type: application/json` - Body (raw, JSON): `{"items": [{"id": 456}, {"id": 457}]}` ### Response Processed as a background job for more than one user. ## Issue an award to a user POST `/learningplan/v1/learningplans/{learning_plan_id}/users/{user_id}/award` **What it does**: Issues an award to a user for a certification. Use this endpoint to record that a user completed a certification, for example when the learner completed the training outside the platform. The learning plan must be a certification (`"type": 2`). ### Parameters | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `learning_plan_id` | integer (path) | Yes | The ID of the certification. | | `user_id` | integer (path) | Yes | The ID of the user who receives the award. | | `completion_date` | string (body) | Yes | The completion date of the award, in UTC and in the format `YYYY-MM-DD HH:mm:ss`. The date cannot be in the future. | ### Example calls **API browser** - Go to: - Service **Learning Plan** - Grouping **Awards** - POST `/learningplans/{learning_plan_id}/users/{user_id}/award` - Enter `learning_plan_id`, `user_id` and the body - Click **Try** **JavaScript (Fetch)** ```javascript const lpId = 123; const userId = 456; const response = await fetch(`https://yourdomain.docebosaas.com/learningplan/v1/learningplans/${lpId}/users/${userId}/award`, {  method: 'POST',  headers: {    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',    'Content-Type': 'application/json'  },  body: JSON.stringify({ completion_date: '2026-11-01 10:00:00' }) }); const data = await response.json(); console.log(data); ``` **Python (requests)** ```python import requests lp_id = 123 user_id = 456 url = f"https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{lp_id}/users/{user_id}/award" headers = {    "Authorization": "Bearer YOUR_ACCESS_TOKEN",    "Content-Type": "application/json" } payload = {"completion_date": "2026-11-01 10:00:00"} response = requests.post(url, headers=headers, json=payload) print(response.json()) ``` **PHP (cURL)** ```php "https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{$lpId}/users/{$userId}/award",    CURLOPT_RETURNTRANSFER => true,    CURLOPT_CUSTOMREQUEST => "POST",    CURLOPT_POSTFIELDS => json_encode(["completion_date" => "2026-11-01 10:00:00"]),    CURLOPT_HTTPHEADER => [        "Authorization: Bearer YOUR_ACCESS_TOKEN",        "Content-Type: application/json"    ], ]); $response = curl_exec($curl); curl_close($curl); echo $response; ``` **Postman** - Method: POST - URL: `https://yourdomain.docebosaas.com/learningplan/v1/learningplans/123/users/456/award` - Headers: `Authorization: Bearer YOUR_ACCESS_TOKEN`, `Content-Type: application/json` - Body (raw, JSON): `{"completion_date": "2026-11-01 10:00:00"}` ### Response The response contains `data.success` (boolean) and `version`. ### Errors | Code | Description | | --- | --- | | `1000` | Generic error. | | `1001` | Invalid parameter. | | `1002` | Missing parameter. | | `3001` | An error occurred while creating the award for the learning plan and the user. | ## Issue awards in bulk POST `/learningplan/v1/learningplans/{learning_plan_id}/award/bulk` **What it does**: Issues awards to multiple users for a certification in one call. The `items` and `options` objects are both required. The learning plan must be a certification (`"type": 2`). ### Parameters | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `learning_plan_id` | integer (path) | Yes | The ID of the certification. | | `items` | array of objects (body) | Yes | The users who receive the award. Each object contains a `user_id`. You can pass from 1 to 2,000 items. | | `options.completion_date` | string (body) | Yes | The completion date of the awards, in UTC and in the format `YYYY-MM-DD HH:mm:ss`. The date cannot be in the future. | ### Example calls **API browser** - Go to: - Service **Learning Plan** - Grouping **Awards** - POST `/learningplans/{learning_plan_id}/award/bulk` - Enter `learning_plan_id` and the body - Click **Try** **JavaScript (Fetch)** ```javascript const lpId = 123; const response = await fetch(`https://yourdomain.docebosaas.com/learningplan/v1/learningplans/${lpId}/award/bulk`, {  method: 'POST',  headers: {    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',    'Content-Type': 'application/json'  },  body: JSON.stringify({    items: [{ user_id: 456 }, { user_id: 457 }],    options: { completion_date: '2026-11-01 10:00:00' }  }) }); const data = await response.json(); console.log(data); ``` **Python (requests)** ```python import requests lp_id = 123 url = f"https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{lp_id}/award/bulk" headers = {    "Authorization": "Bearer YOUR_ACCESS_TOKEN",    "Content-Type": "application/json" } payload = {    "items": [{"user_id": 456}, {"user_id": 457}],    "options": {"completion_date": "2026-11-01 10:00:00"} } response = requests.post(url, headers=headers, json=payload) print(response.json()) ``` **PHP (cURL)** ```php "https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{$lpId}/award/bulk",    CURLOPT_RETURNTRANSFER => true,    CURLOPT_CUSTOMREQUEST => "POST",    CURLOPT_POSTFIELDS => json_encode([        "items" => [["user_id" => 456], ["user_id" => 457]],        "options" => ["completion_date" => "2026-11-01 10:00:00"]    ]),    CURLOPT_HTTPHEADER => [        "Authorization: Bearer YOUR_ACCESS_TOKEN",        "Content-Type: application/json"    ], ]); $response = curl_exec($curl); curl_close($curl); echo $response; ``` **Postman** - Method: POST - URL: `https://yourdomain.docebosaas.com/learningplan/v1/learningplans/123/award/bulk` - Headers: `Authorization: Bearer YOUR_ACCESS_TOKEN`, `Content-Type: application/json` - Body (raw, JSON): `{"items": [{"user_id": 456}, {"user_id": 457}], "options": {"completion_date": "2026-11-01 10:00:00"}}` ### Response The call is processed synchronously or as a background job. A synchronous response contains: - `data.errors` (boolean) - `data.items`, where each item has: - `id` - `success` - `error_code` - `message` A background job response contains `data.job_descriptor` (with `id` and `name`). Check the job status endpoint referenced in [Overview of API services and endpoints](/v1/docs/overview-of-api-services-and-endpoints). ### Errors | Code | Description | | --- | --- | | `1001` | Invalid parameter. | | `1002` | Missing parameter. | | `1003` | Error while creating the background job. | | `1004` | The processing requires more operations than the maximum allowed. | ## Update the dates of an award PATCH `/learningplan/v1/awards/{award_id}/dates` **What it does**: Updates the completion date and the renewal window of one award. ### Parameters | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `award_id` | integer (path) | Yes | The ID of the award. Use the `award.id` value returned in [List certifications to renew](/v1/docs/certification-and-retraining-apis-how-to-guide#18-list-certifications-to-renew) to find it. | | `completion_date` | string (body) | No | The new completion date of the award, in UTC. | | `renewal_window` | object (body) | No | The renewal window of the award. If you pass this object, you must include `date_start`. | | `renewal_window.date_start` | string or `null` (body) | Yes, if you pass `renewal_window` | The start of the renewal window, as a UTC date. Pass `null` to remove the renewal window. | | `renewal_window.date_end` | string or `null` (body) | No | The end of the renewal window, as a UTC date. The window ends at 23:59:59 on that date. You cannot set this value when `date_start` is `null`. | ### Example calls **API browser** - Go to: - Service **Learning Plan** - Grouping **Awards** - PATCH `/awards/{award_id}/dates` - Enter `award_id` and the body - Click **Try** **JavaScript (Fetch)** ```javascript const awardId = 789; const response = await fetch(`https://yourdomain.docebosaas.com/learningplan/v1/awards/${awardId}/dates`, {  method: 'PATCH',  headers: {    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',    'Content-Type': 'application/json'  },  body: JSON.stringify({ completion_date: '2026-11-01 10:00:00' }) }); const data = await response.json(); console.log(data); ``` **Python (requests)** ```python import requests award_id = 789 url = f"https://yourdomain.docebosaas.com/learningplan/v1/awards/{award_id}/dates" headers = {    "Authorization": "Bearer YOUR_ACCESS_TOKEN",    "Content-Type": "application/json" } payload = {"completion_date": "2026-11-01 10:00:00"} response = requests.patch(url, headers=headers, json=payload) print(response.json()) ``` **PHP (cURL)** ```php "https://yourdomain.docebosaas.com/learningplan/v1/awards/{$awardId}/dates",    CURLOPT_RETURNTRANSFER => true,    CURLOPT_CUSTOMREQUEST => "PATCH",    CURLOPT_POSTFIELDS => json_encode(["completion_date" => "2026-11-01 10:00:00"]),    CURLOPT_HTTPHEADER => [        "Authorization: Bearer YOUR_ACCESS_TOKEN",        "Content-Type: application/json"    ], ]); $response = curl_exec($curl); curl_close($curl); echo $response; ``` **Postman** - Method: PATCH - URL: `https://yourdomain.docebosaas.com/learningplan/v1/awards/789/dates` - Headers: `Authorization: Bearer YOUR_ACCESS_TOKEN`, `Content-Type: application/json` - Body (raw, JSON): `{"completion_date": "2026-11-01 10:00:00"}` ### Response The response contains `data.success` (boolean). ### Errors | Code | Description | | --- | --- | | `1000` | Award not found. | | `1001` | Invalid parameter. | | `1002` | Missing parameter. | | `1003` | Data parsing error. | ## Update the dates of multiple awards PATCH `/learningplan/v1/learningplans/{learning_plan_id}/awards/dates/bulk` **What it does**: Updates the completion date, the expiration date and the renewal window of multiple awards of a certification in one call. The `options` object is required, and it must contain at least one of its fields. ### Parameters | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `learning_plan_id` | integer (path) | Yes | The ID of the certification. | | `items` | array of objects (body) | Yes | The awards to update. Each object contains an `award_id`. You can pass from 1 to 2,000 items. | | `options.completion_date` | string (body) | No | The new completion date, in UTC. The value cannot be `null`. | | `options.expiration_date` | string or `null` (body) | No | The new expiration date, as a UTC date. Pass `null` to remove the expiration date. | | `options.renewal_window.date_start` | string or `null` (body) | Yes, if you pass `renewal_window` | The start of the renewal window, in UTC. Pass `null` to remove the renewal window. | | `options.renewal_window.date_end` | string or `null` (body) | No | The end of the renewal window, in UTC. This value cannot be set when `date_start` is `null`. | ### Example calls **API browser** - Go to: - Service **Learning Plan** - Grouping **Awards** - PATCH `/learningplans/{learning_plan_id}/awards/dates/bulk` - Enter `learning_plan_id` and the body - Click **Try** **JavaScript (Fetch)** ```javascript const lpId = 123; const response = await fetch(`https://yourdomain.docebosaas.com/learningplan/v1/learningplans/${lpId}/awards/dates/bulk`, {  method: 'PATCH',  headers: {    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',    'Content-Type': 'application/json'  },  body: JSON.stringify({    items: [{ award_id: 789 }, { award_id: 790 }],    options: {      completion_date: '2026-11-01 10:00:00',      expiration_date: '2027-11-01'    }  }) }); const data = await response.json(); console.log(data); ``` **Python (requests)** ```python import requests lp_id = 123 url = f"https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{lp_id}/awards/dates/bulk" headers = {    "Authorization": "Bearer YOUR_ACCESS_TOKEN",    "Content-Type": "application/json" } payload = {    "items": [{"award_id": 789}, {"award_id": 790}],    "options": {        "completion_date": "2026-11-01 10:00:00",        "expiration_date": "2027-11-01"    } } response = requests.patch(url, headers=headers, json=payload) print(response.json()) ``` **PHP (cURL)** ```php "https://yourdomain.docebosaas.com/learningplan/v1/learningplans/{$lpId}/awards/dates/bulk",    CURLOPT_RETURNTRANSFER => true,    CURLOPT_CUSTOMREQUEST => "PATCH",    CURLOPT_POSTFIELDS => json_encode([        "items" => [["award_id" => 789], ["award_id" => 790]],        "options" => [            "completion_date" => "2026-11-01 10:00:00",            "expiration_date" => "2027-11-01"        ]    ]),    CURLOPT_HTTPHEADER => [        "Authorization: Bearer YOUR_ACCESS_TOKEN",        "Content-Type: application/json"    ], ]); $response = curl_exec($curl); curl_close($curl); echo $response; ``` **Postman** - Method: PATCH - URL: `https://yourdomain.docebosaas.com/learningplan/v1/learningplans/123/awards/dates/bulk` - Headers: `Authorization: Bearer YOUR_ACCESS_TOKEN`, `Content-Type: application/json` - Body (raw, JSON): `{"items": [{"award_id": 789}, {"award_id": 790}], "options": {"completion_date": "2026-11-01 10:00:00", "expiration_date": "2027-11-01"}}` ### Response The call is processed synchronously or as a background job. A synchronous response contains: - `data.errors` (boolean) - `data.items`, where each item has: - `award_id` - `success` - `error_code` - `error_message` A background job response contains `data.job_descriptor`. Check the job status endpoint referenced in [Overview of API services and endpoints](/v1/docs/overview-of-api-services-and-endpoints). ### Errors | Code | Description | | --- | --- | | `1000` | Generic error. | | `1001` | Invalid parameter. | | `1002` | Missing parameter. | | `1003` | Error while creating the background job. | | `1004` | The processing requires more operations than the maximum allowed. | | `1005` | Invalid learning plan ID. | | `1014` | Learning plan not found. | For an individual item, the possible error codes are `1000` (something went wrong), `1404` (invalid item) and `1403` (permission denied). ## List certifications to renew GET `/learningplan/v1/awards/to_renew` **What it does**: Returns the certifications that are eligible for renewal, including both new certifications and legacy certifications. This is the data behind the Certifications to Renew widget. ### Parameters (query string) | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `status` | string | No | Filters by award status. Possible values are: - `valid` - `expired` - `expiring` - `in_renewal` | | `sort_attr` | string | No | The field to sort by. Default `expiration_date`. | | `sort_dir` | string | No | The sort direction. Default `asc`. | | `page` | integer | No | The page number. | | `page_size` | integer | No | The number of items per page. | ### Example calls **API browser** - Go to: - Service **Learning Plan** - Grouping **Awards** - GET `/awards/to_renew` - Enter the query parameters - Click **Try** **JavaScript (Fetch)** ```javascript const response = await fetch('https://yourdomain.docebosaas.com/learningplan/v1/awards/to_renew?status=expiring&page=1&page_size=10', {  method: 'GET',  headers: {    'Authorization': 'Bearer YOUR_ACCESS_TOKEN'  } }); const data = await response.json(); console.log(data); ``` **Python (requests)** ```python import requests url = "https://yourdomain.docebosaas.com/learningplan/v1/awards/to_renew" headers = {"Authorization": "Bearer YOUR_ACCESS_TOKEN"} params = {"status": "expiring", "page": 1, "page_size": 10} response = requests.get(url, headers=headers, params=params) print(response.json()) ``` **PHP (cURL)** ```php "https://yourdomain.docebosaas.com/learningplan/v1/awards/to_renew?status=expiring&page=1&page_size=10",    CURLOPT_RETURNTRANSFER => true,    CURLOPT_CUSTOMREQUEST => "GET",    CURLOPT_HTTPHEADER => [        "Authorization: Bearer YOUR_ACCESS_TOKEN"    ], ]); $response = curl_exec($curl); curl_close($curl); echo $response; ``` **Postman** - Method: GET - URL: `https://yourdomain.docebosaas.com/learningplan/v1/awards/to_renew?status=expiring&page=1&page_size=10` - Headers: `Authorization: Bearer YOUR_ACCESS_TOKEN` ### Response Each item in the response has the following fields: - `id` - `type` (`legacy_certification` or `new_certification`) - `title` - `language` - `assigned_courses_count` - `duration_minutes` - `enrollment_status` - `enrollment_date` - `enrollment_validity_date_start` - `enrollment_validity_date_end` - `cover_img_url` - `can_enter` - `can_enter_reason` Legacy certifications also return: - `expiration_date` - `award_date` - `in_renew` New certifications return an `award` object with: - `id` - `status` (`valid`, `expiring` or `expired`) - `expiration_date` - `award_date` - `in_renew` - `renew_date` - `renewal_mode` (`anytime` or `window`) - `renewal_window_date_start` - `renewal_window_date_end` The response also contains the paging fields: - `count` - `has_more_data` - `current_page` - `current_page_size` - `total_page_count` - `total_count` - `sort` A learning plan is a curated sequence of courses including e-learning and Instructor-Led Training (ILT) that forms the backbone of personalized learning journeys for individuals, branches or groups. These plans serve as crafted learning experiences that guide learners through structured pathways of courses and resources to foster individual growth and organizational culture. Learning plans allow administrators to facilitate employee skill development or streamline certification programs by creating personalized paths. They empower organizations to shape tailored experiences by integrating features such as mandatory requirements, prerequisites, enrollment rules and certifications to meet the unique needs and objectives of the company. A webhook is a method for one application to automatically send real-time data to another application when a specific event occurs. It acts as a user-defined HTTP callback, triggered by events, rather than the traditional polling method where applications repeatedly query for updates. A background job is a mass action within the Docebo platform that is processed in the background, allowing a user to continue working, log out, or close their browser. Common examples include importing large lists of users, courses, or training materials.