Certificates
A certificate is a template that can be issued to employees — optionally with a validity period after which it expires. Use these endpoints to manage certificate templates, assign them to users, and inspect who holds them. Certificate categories are managed via Certificate Types.
Required scope: read:certificates for the GET endpoints, write:certificates for the rest.
Endpoints
/api/open/v1/certificatesList certificate templates (paginated)
/api/open/v1/certificates/{id}Get a single certificate template
/api/open/v1/certificatesCreate a certificate template (draft by default)
/api/open/v1/certificates/{id}Update a certificate template (PUT works the same)
/api/open/v1/certificates/{id}Soft delete a certificate template (returns 204)
/api/open/v1/certificates/{id}/usersList users who hold this certificate, with their state
/api/open/v1/certificates/assignAssign certificates to users
/api/open/v1/certificates/{id}/resetReset / expire a certificate for users (returns 204)
/api/open/v1/users/{id}/certificatesList all certificates held by a user
/api/open/v1/users/{id}/certificates/{certificate_id}Get a single certificate held by a user
List
curl "https://customer.monotree.com/api/open/v1/certificates?is_published=true" \
-H "Authorization: Bearer mono_your_token_here"| Query param | Notes |
|---|---|
is_published | true returns published templates only, false returns drafts only. Omit for all. |
title | Filter by title (partial match, any locale). |
since | ISO 8601 — filter by created_at. |
page, per_page | Standard pagination — per_page max 50. |
Newest first, by created_at. Deleted certificates are not listed and return 404.
Create
A certificate is created as a draft by default. Pass is_published: true to publish it immediately.
curl -X POST https://customer.monotree.com/api/open/v1/certificates \
-H "Authorization: Bearer mono_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"title": "Food Safety",
"description": "Awarded on completion of the food safety course.",
"validity_months": 12,
"reset_days_before_expiry": 30,
"certificate_type_id": 3,
"is_published": true
}'Request body
| Field | Type | Notes |
|---|---|---|
title | string | Required. Max 255 characters. |
description | string | Optional. Max 50,000 characters. |
validity_months | integer | Optional, 0 or more. Months until an issued certificate expires. Omit, null or 0 = never expires. |
reset_days_before_expiry | integer | Optional. 14–365. Cannot be null; when omitted the platform default applies. |
certificate_type_id | integer | Optional. A certificate type id, or null. An unknown id returns 422. |
is_published | boolean | Optional. true publishes immediately. Defaults to false (draft). |
Returns 201 with the certificate object.
PUT and PATCH accept the same fields — all optional — and only change what you send. Setting is_published toggles the published state. Changing validity_months also recalculates the expiry of every holder's current, unexpired certificate from its issue date (or removes the expiry when set to null or 0). Returns 200 with the certificate object.
DELETE /certificates/{id} soft-deletes the template and returns 204. It is no longer listed, and its holdings no longer appear under GET /users/{id}/certificates.
Assign certificates to users
Mirrors the in-app bulk assignment. Each user is issued the certificate as of their completed_at date; the expiry is completed_at plus the template's validity_months (none when the template has no validity). A user who already holds the certificate unexpired is skipped, and so is one already issued it on that completed_at, expired or not, so sending the same assignment again issues nothing twice.
| Field | Type | Notes |
|---|---|---|
assignments | array | Required, at least one. |
assignments[].certificate_id | integer | Required. An unknown or deleted certificate returns 422. |
assignments[].users | array | Required, at least one. |
assignments[].users[].user_id | integer | Required. An unknown or deleted user returns 422. |
assignments[].users[].completed_at | date | Required. ISO 8601 date or datetime. |
The whole request is validated before anything is issued: when one entry is refused, no certificate is issued.
curl -X POST https://customer.monotree.com/api/open/v1/certificates/assign \
-H "Authorization: Bearer mono_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"assignments": [
{
"certificate_id": 12,
"users": [
{ "user_id": 501, "completed_at": "2026-06-01" },
{ "user_id": 502, "completed_at": "2026-06-01" }
]
}
]
}'Returns 204 No Content.
Reset / remove for users
Resets the completion the certificate is linked to (for example a course) for the given users, and — with expire: true — pulls any future expiry back to now, so the users no longer hold a valid certificate. This is how a certificate is revoked for a user; the platform keeps the issuance history rather than hard-deleting it. If user_ids is omitted or empty, every user who holds or has held the certificate is targeted.
curl -X POST https://customer.monotree.com/api/open/v1/certificates/12/reset \
-H "Authorization: Bearer mono_your_token_here" \
-H "Content-Type: application/json" \
-d '{ "user_ids": [501], "expire": true, "notify": false }'| Field | Type | Notes |
|---|---|---|
user_ids | array of integers | Optional. Defaults to all holders. An unknown user id returns 422. |
expire | boolean | Optional. Pull a future expiry back to now (revoke). |
notify | boolean | Optional, only with expire. Sends the certificate-expired notification to the users whose holding this call expired and who can still access the linked completable. |
Returns 204 No Content.
Response
{
"data": {
"id": 12,
"article_id": 94,
"title": "Food Safety",
"description": "Awarded on completion of the food safety course.",
"validity_months": 12,
"reset_days_before_expiry": 30,
"active_users_count": 14,
"is_published": true,
"certificate_type": { "id": 3, "title": "Compliance", "is_published": true, "published_at": "2026-05-01T10:00:00+02:00", "created_at": "2026-05-01T10:00:00+02:00", "updated_at": "2026-05-01T10:00:00+02:00" },
"published_at": "2026-06-01T10:00:00+02:00",
"created_at": "2026-06-01T10:00:00+02:00",
"updated_at": "2026-06-01T10:00:00+02:00"
}
}active_users_count is the number of users who hold the certificate now: their holding has no expiry or expires in the future. certificate_type is the certificate type object, or null.
A user's certificates
GET /users/{id}/certificates returns every certificate a user holds or has held, each with the dates of the latest issuance and any earlier issuances under history (newest first). Not paginated. Certificates that have been deleted are left out. is_expired is false when the certificate has no expiry. Returns 404 for an unknown user. GET /users/{id}/certificates/{certificate_id} returns one entry in the same shape, or 404 when the user has never held the certificate.
{
"data": [
{
"id": 12,
"title": "Food Safety",
"description": "Awarded on completion of the food safety course.",
"validity_months": 12,
"issued_at": "2026-06-01T00:00:00+02:00",
"expires_at": "2027-06-01T00:00:00+02:00",
"is_expired": false,
"certificate_type": { "id": 3, "title": "Compliance", "is_published": true, "published_at": "2026-05-01T10:00:00+02:00", "created_at": "2026-05-01T10:00:00+02:00", "updated_at": "2026-05-01T10:00:00+02:00" },
"history": []
}
]
}A certificate's holders
GET /certificates/{id}/users returns every user who holds or has held the certificate, sorted by first and last name, with their user_state. Not paginated.
{
"data": [
{
"id": 501,
"name": "Jane Doe",
"email": "jane@example.com",
"avatar_url": null,
"user_state": {
"has_certificate": true,
"issued_at": "2026-06-01T00:00:00+02:00",
"expires_at": "2027-06-01T00:00:00+02:00",
"is_expired": false,
"last_nudged_at": null,
"completables": [
{ "id": 8, "title": "Food Safety Basics", "completable_type": "course" }
],
"history": [
{ "issued_at": "2025-05-20T00:00:00+02:00", "expires_at": "2026-05-20T00:00:00+02:00" }
]
}
}
]
}has_certificate is true while the latest issuance has no expiry or has not expired. completables lists what the latest issuance was earned through. last_nudged_at is when the user was last reminded about the certificate, or null. history holds the earlier issuances, newest first.
article_id links the entry's rich content — manage it through the articles endpoints.