# Progress · Management API

> Management API reference for progress: GET /sites/{id}/deliverables/{deliverableId}/progress, POST /sites/{id}/deliverables/{deliverableId}/progress/{contactId}/reset, POST /sites/{id}/deliverables/{deliverableId}/progress/{contactId}/complete.

> Markdown version of https://kanorio.com/en/developers/management-api/progress. Append `.md` to any developer docs URL for this format. Index: https://kanorio.com/llms.txt · MCP: https://app.kanorio.com/api/mcp

Base URL: `https://app.kanorio.com/api/v1` · [Errors](https://kanorio.com/en/developers/basics/errors) · [Pagination](https://kanorio.com/en/developers/basics/pagination)

### List progress

`GET /sites/{id}/deliverables/{deliverableId}/progress`

- Stability: beta
- Permission: `customers:view`
- Rate limit: default (120/minute per token)
- MCP tool: `progress_list`

List learners of one deliverable: progress (percent, completed, per-unit status), viewing (watchedSeconds, lastPlayedAt, per-unit unitCoverage) and downloads (count, last time). One row per contact that has an active entitlement; for DOWNLOAD deliverables look at downloads. Aggregated course and unit figures are in deliverables.insights and deliverable_units.insights. Errors: FORBIDDEN, DELIVERABLE_NOT_FOUND, INVALID_CURSOR.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `deliverableId` | path | string | yes | Deliverable id. Get it from deliverables.list. |
| `limit` | query | integer | no | Maximum number of items to return, 1–100. Defaults to 20. |
| `cursor` | query | string | no | Opaque cursor from the previous page's nextCursor. Omit for the first page. |
| `contactId` | query | string | no | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `data` | object[] | yes | — |
| `data[].contactId` | string | yes | — |
| `data[].percent` | integer | yes | — |
| `data[].completed` | boolean | yes | Completion snapshot: set the first time the completion rule is met and never reverts, even if units are added later. |
| `data[].completedAt` | string \| null | yes | — |
| `data[].startedAt` | string \| null | yes | First progress record. |
| `data[].lastActivityAt` | string \| null | yes | Latest progress report or download. |
| `data[].certificate` | object \| null | yes | — |
| `data[].certificate.serial` | string | yes | — |
| `data[].certificate.issuedAt` | string | yes | — |
| `data[].units` | object | yes | — |
| `data[].watchedSeconds` | integer | yes | Wall-clock seconds of media playback across all units (2x speed for 10 minutes counts 5; rewatching counts again). |
| `data[].lastPlayedAt` | string \| null | yes | Latest media playback; null when the learner has only opened non-media units or downloaded files. |
| `data[].unitCoverage` | object | yes | Per media unit: share of the file actually watched (union of watched ranges / duration, 0-100). Units without a duration or never played are omitted. |
| `data[].downloads` | object | yes | Download events across all units (every entitlement source counts). |
| `data[].downloads.count` | integer | yes | — |
| `data[].downloads.lastAt` | string \| null | yes | — |
| `hasMore` | boolean | yes | True when another page exists; pass nextCursor to fetch it. |
| `nextCursor` | string \| null | yes | Cursor for the next page, or null on the last page. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `DELIVERABLE_NOT_FOUND` | 404 | — |
| `INVALID_CURSOR` | 400 | — |

```bash
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/deliverables/{deliverableId}/progress" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
```

```json
{
  "data": [
    {
      "contactId": "contact_example01",
      "percent": 50,
      "completed": false,
      "completedAt": null,
      "startedAt": "2026-10-06T08:00:00.000Z",
      "lastActivityAt": "2026-10-06T08:00:00.000Z",
      "certificate": null,
      "units": {
        "unit_video01": "completed",
        "unit_text01": "in-progress"
      },
      "watchedSeconds": 1860,
      "lastPlayedAt": "2026-10-06T08:00:00.000Z",
      "unitCoverage": {
        "unit_video01": 94
      },
      "downloads": {
        "count": 2,
        "lastAt": "2026-10-06T08:00:00.000Z"
      }
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

### Progress reset

`POST /sites/{id}/deliverables/{deliverableId}/progress/{contactId}/reset`

- Stability: beta
- Permission: `customers:manage`
- Rate limit: default (120/minute per token)
- MCP tool: `progress_reset`

Reset one learner on one deliverable: deletes their unit progress, quiz attempts and completion record so they start over. Certificates already issued are kept. Errors: FORBIDDEN, DELIVERABLE_NOT_FOUND, CONTACT_NOT_FOUND.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `deliverableId` | path | string | yes | Deliverable id. Get it from deliverables.list. |
| `contactId` | path | string | yes | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `contactId` | string | yes | — |
| `percent` | integer | yes | — |
| `completed` | boolean | yes | — |
| `completedAt` | string \| null | yes | — |
| `units` | object | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `DELIVERABLE_NOT_FOUND` | 404 | — |
| `CONTACT_NOT_FOUND` | 404 | — |

```bash
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/deliverables/{deliverableId}/progress/{contactId}/reset" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
```

```json
{
  "contactId": "contact_example01",
  "percent": 0,
  "completed": false,
  "completedAt": null,
  "units": {}
}
```

### Progress complete

`POST /sites/{id}/deliverables/{deliverableId}/progress/{contactId}/complete`

- Stability: beta
- Permission: `customers:manage`
- Rate limit: default (120/minute per token)
- MCP tool: `progress_complete`

Mark one learner complete on one deliverable on their behalf: every unit that counts toward completion is marked completed, the completion record is written (ruleType manual) and the certificate is issued when enabled. Errors: FORBIDDEN, DELIVERABLE_NOT_FOUND, CONTACT_NOT_FOUND.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `deliverableId` | path | string | yes | Deliverable id. Get it from deliverables.list. |
| `contactId` | path | string | yes | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `contactId` | string | yes | — |
| `percent` | integer | yes | — |
| `completed` | boolean | yes | — |
| `completedAt` | string \| null | yes | — |
| `units` | object | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `DELIVERABLE_NOT_FOUND` | 404 | — |
| `CONTACT_NOT_FOUND` | 404 | — |

```bash
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/deliverables/{deliverableId}/progress/{contactId}/complete" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
```

```json
{
  "contactId": "contact_example01",
  "percent": 100,
  "completed": true,
  "completedAt": "2026-10-06T08:00:00.000Z",
  "units": {
    "unit_video01": "completed",
    "unit_text01": "completed"
  }
}
```
