Achievements

Achievements are badges employees earn or are given: employee of the month, a completion badge for finishing linked content (courses, handbooks, journeys), an employment badge after a number of months of tenure, a tasks badge after a number of completed to-do tasks, or a custom badge handed out by hand. Each achievement has a type, an emoji, a visibility (public — celebrated with department colleagues in the app — or personal, shown only to the employee and their managers) and, for the automatic types, a trigger config. An award is one achievement given to one user on a date — repeat awards (employee of the month twice) are separate awards. New awards are also announced by the monotree.achievement.awarded outgoing webhook.

Required scope: read:achievements to read, write:achievements to create, update and award. The achievements feature must be enabled for the company — otherwise every endpoint returns 403 with the message The achievements feature is not enabled.

Achievements and awards cannot be deleted through the API.

Endpoints

GET
/api/open/v1/achievements

List achievements (paginated)

GET
/api/open/v1/achievements/{id}

Get a single achievement

POST
/api/open/v1/achievements

Create an achievement (draft by default)

PATCH
/api/open/v1/achievements/{id}

Update an achievement

GET
/api/open/v1/achievements/{id}/awards

List every award of an achievement, newest first (paginated)

POST
/api/open/v1/achievements/{id}/awards

Award an achievement to users

GET
/api/open/v1/users/{id}/achievements

List the awards a user holds on published achievements

List

curl "https://customer.monotree.com/api/open/v1/achievements?is_published=true&type=employee_of_the_month" \
  -H "Authorization: Bearer mono_your_token_here"
Query paramNotes
is_publishedtrue returns published achievements only, false returns drafts only. Omit for all.
typeemployee_of_the_month, completion, employment, tasks or custom. Any other value returns 422.
visibilitypublic or personal. Any other value returns 422.
sinceISO 8601 — filter by created_at.
page, per_pageStandard pagination — per_page max 50.

Achievements are ordered newest first. Drafts are returned too — is_published tells. GET /achievements/{id} returns drafts as well.

Create

An achievement is created as a draft by default. Pass is_published: true to publish it immediately. The response is 201 with the achievement object.

curl -X POST https://customer.monotree.com/api/open/v1/achievements \
  -H "Authorization: Bearer mono_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "custom",
    "title": "Team player",
    "description": "Always there for colleagues.",
    "emoji": "🤝",
    "visibility": "public",
    "translations": {
      "title": { "da": "Holdspiller" }
    },
    "is_published": true
  }'

Request body

FieldTypeNotes
typestringRequired. employee_of_the_month, completion, employment, tasks or custom. Cannot be changed later.
titlestringRequired, except for employee_of_the_month: without a title it is named "Employee of the month" in every active language. Max 255 characters. Written in the default content language.
descriptionstringOptional. Max 50,000 characters.
emojistringOptional, max 16 characters. Defaults to 👑 for employee of the month, 🏆 for completion, ⭐ for employment, ✅ for tasks and 🤩 for custom.
visibilitystringOptional. public or personal. Defaults to public for employee of the month and employment, personal for completion, tasks and custom.
trigger_configobjectCompletion, employment and tasks only — see Triggers. Sending it for another type returns 422.
translationsobjectOptional. { "title": { "da": "..." }, "description": { "da": "..." } }. Every locale must be an active language (see Locales); writing one activates it on the achievement. The title in the default content language cannot be emptied here: an empty or null value for it returns 422 with errors.translations.title.<locale> The title in the default content language cannot be empty.
is_publishedbooleanOptional. true publishes immediately. Defaults to false (draft).

Triggers

Completion, employment and tasks achievements are awarded automatically, so they need a complete trigger_config before they can be published:

Typetrigger_configAwarded
completion{ "completable_type": "course", "completable_ids": [42] } — handbook, course or journey. Every id must be something the CMS lets you link: a published handbook or course, or a published onboarding or custom journey (preboarding and offboarding journeys can't be linked)When an employee completes the linked content
employment{ "months": 12 } — at least 1On the daily check, once an employee has been employed that many months
tasks{ "count": 100 } — at least 1When an employee checks off their count-th to-do task, across all lists. Credit goes to whoever checks the task; unchecking a task takes it out of the count

A trigger_config you send has to be complete; an incomplete one returns 422. Publishing without a complete trigger config also returns 422, and nothing is created or changed.

Update

curl -X PATCH https://customer.monotree.com/api/open/v1/achievements/12 \
  -H "Authorization: Bearer mono_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{ "description": "Chosen by the department managers every month.", "is_published": true }'

PATCH and PUT accept the same fields as create, all optional, and only change what you send. The response is the achievement object.

  • title and description are written in the default content language. translations are merged: languages you leave out keep their text. Emptying the default-language title through translations returns 422, as on create.
  • type cannot be changed. Sending a different type returns 422; sending the current one is accepted, so you can send back an achievement you read.
  • is_published toggles the published state. A published achievement's trigger config has to stay complete, so clearing it or sending an incomplete one returns 422.
  • An unknown id returns 404.

Response

{
  "data": {
    "id": 12,
    "title": "Employee of the month",
    "description": "Chosen by the department managers.",
    "translations": {
      "title": { "en": "Employee of the month", "da": "Månedens medarbejder" },
      "description": { "en": "Chosen by the department managers." }
    },
    "emoji": "👑",
    "colors": ["#CB961A", "#28BE8A", "#D94BA3"],
    "type": "employee_of_the_month",
    "visibility": "public",
    "trigger_type": null,
    "trigger_config": null,
    "awarded_users_count": 37,
    "is_published": true,
    "published_at": "2026-06-01T10:00:00+02:00",
    "created_at": "2026-05-20T09:00:00+02:00",
    "updated_at": "2026-06-01T10:00:00+02:00"
  }
}
FieldNotes
typeemployee_of_the_month, completion, employment, tasks or custom.
colorsThe emoji's own three colours as hex values, as the app draws the badge: the ring takes the first, the twinkles all three. null when the emoji has no colours of its own or is not in the emoji picker (the apps then draw it in gold). Skin tones are ignored.
visibilitypublic — celebrated with department colleagues in the app — or personal, shown only to the employee and their managers.
trigger_typecompletion, tenure or task_count for the automatic types, null for the manual ones (employee of the month, custom).
trigger_config{ "completable_type": "handbook" | "course" | "journey", "completable_ids": [...] } for completion, { "months": 12 } for tenure, { "count": 100 } for task_count, null otherwise.
awarded_users_countNumber of awards handed out.
translationsEvery locale of the title and description.

An achievement's awards

GET /achievements/{id}/awards returns every award, newest first, with the holder embedded as the user object of /users, without roles. department is the department the award was given in (employee of the month for Copenhagen) or null; assigned_by is the CMS user who handed it out, the company's own user for awards given through the API, and null for automatic awards. Awards of deleted users are left out. since filters by the award's created_at; standard pagination, per_page max 50.

{
  "data": [
    {
      "id": 4711,
      "awarded_at": "2026-08-01T00:00:00+02:00",
      "is_manually_added": true,
      "department": { "id": 41, "name": "Copenhagen", "type": "department", "created_at": "2025-09-01T10:00:00+02:00" },
      "assigned_by": { "id": 5, "name": "Sam Doe", "email": "sam@example.com", "avatar_url": null, "is_manager": true, "...": "…rest of the user object…" },
      "user": { "id": 501, "name": "Jane Doe", "email": "jane@example.com", "avatar_url": null, "is_manager": false, "groups": [ { "id": 41, "name": "Copenhagen", "type": "department", "is_manager": false, "created_at": "2025-09-01T10:00:00+02:00" } ], "...": "…rest of the user object…" },
      "created_at": "2026-08-03T09:12:41+02:00",
      "updated_at": "2026-08-03T09:12:41+02:00"
    },
    {
      "id": 4690,
      "awarded_at": "2026-07-01T00:00:00+02:00",
      "is_manually_added": true,
      "department": { "id": 42, "name": "Aarhus", "type": "department", "created_at": "2025-09-01T10:00:00+02:00" },
      "assigned_by": { "id": 5, "name": "Sam Doe", "email": "sam@example.com", "...": "…" },
      "user": { "id": 502, "name": "Alex Doe", "email": "alex@example.com", "...": "…" },
      "created_at": "2026-07-02T08:00:00+02:00",
      "updated_at": "2026-07-02T08:00:00+02:00"
    }
  ],
  "links": { "first": "...", "last": "...", "prev": null, "next": "..." },
  "meta": { "current_page": 1, "last_page": 2, "per_page": 25, "total": 37, "from": 1, "to": 25 }
}

Award users

POST /achievements/{id}/awards gives the achievement to one or more users, the same way an admin hands it out in the CMS. Requires write:achievements.

curl -X POST https://customer.monotree.com/api/open/v1/achievements/12/awards \
  -H "Authorization: Bearer mono_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "awards": [
      { "user_id": 501, "awarded_at": "2026-09-01", "department_id": 41 },
      { "user_id": 502, "awarded_at": "2026-09-01" }
    ]
  }'
FieldTypeNotes
awardsarrayRequired, 1 to 100 rows.
awards[].user_idintegerRequired. An employee from /users — kiosk, bot and unregistered users return 422.
awards[].awarded_atdateRequired. The day the award is for, e.g. 2026-09-01 (the first of the month for employee of the month). A time sent along is dropped. Can be back-dated.
awards[].department_idintegerThe department the award is given in — it must be one of the user's departments. Required for employee of the month, optional for the other types.

The achievement must be published — a draft returns 422, because an award on a draft would never be announced. The response is 201 with one award object per row, in the order sent — the same object as GET /achievements/{id}/awards, with the user embedded:

{
  "data": [
    {
      "id": 4712,
      "awarded_at": "2026-09-01T00:00:00+02:00",
      "is_manually_added": true,
      "department": { "id": 41, "name": "Copenhagen", "type": "department", "created_at": "2025-09-01T10:00:00+02:00" },
      "assigned_by": { "id": 8, "name": "Acme", "type": "customer", "...": "…" },
      "user": { "id": 501, "name": "Jane Doe", "email": "jane@example.com", "...": "…" },
      "created_at": "2026-09-14T11:30:02+02:00",
      "updated_at": "2026-09-14T11:30:02+02:00"
    },
    {
      "id": 4713,
      "awarded_at": "2026-09-01T00:00:00+02:00",
      "is_manually_added": true,
      "department": null,
      "assigned_by": { "id": 8, "name": "Acme", "type": "customer", "...": "…" },
      "user": { "id": 502, "name": "Alex Doe", "email": "alex@example.com", "...": "…" },
      "created_at": "2026-09-14T11:30:02+02:00",
      "updated_at": "2026-09-14T11:30:02+02:00"
    }
  ]
}
  • assigned_by is always the company's own user (type: customer, named after the company), whichever user the token acts as — so employees see the award as given by the company, not by a colleague. On the rare platform without a company user it is the system user (type: system).
  • The request is all or nothing: if any row is invalid, the response is 422 naming the row (e.g. awards.1.user_id) and nobody is awarded.
  • A row identical to an existing award — same user and same awarded_at — returns that award instead of creating another, so a retried request does not hand it out twice. The same user on a different day is a new award (employee of the month twice).
  • Each new award on a published achievement is announced like a CMS award: the employee gets a push notification, public awards are shown to their department colleagues, and the monotree.achievement.awarded webhook fires.

A user's achievements

GET /users/{id}/achievements returns the awards a user holds on published achievements, newest first, with the achievement embedded. Not paginated. Awards on draft achievements are left out — this is what the employee sees in-app. Returns 404 for kiosk, bot and shadow users, for users who have not registered yet, for deactivated and deleted users, and for an unknown id.

{
  "data": [
    {
      "id": 4711,
      "awarded_at": "2026-08-01T00:00:00+02:00",
      "is_manually_added": true,
      "department": { "id": 41, "name": "Copenhagen", "type": "department", "created_at": "2025-09-01T10:00:00+02:00" },
      "assigned_by": { "id": 5, "name": "Sam Doe", "email": "sam@example.com", "...": "…" },
      "achievement": { "id": 12, "title": "Employee of the month", "emoji": "👑", "type": "employee_of_the_month", "visibility": "public", "awarded_users_count": 37, "is_published": true, "...": "…" },
      "created_at": "2026-08-03T09:12:41+02:00",
      "updated_at": "2026-08-03T09:12:41+02:00"
    },
    {
      "id": 3980,
      "awarded_at": "2026-07-14T09:30:12+02:00",
      "is_manually_added": false,
      "department": null,
      "assigned_by": null,
      "achievement": { "id": 8, "title": "Food safety certified", "emoji": "🏆", "type": "completion", "visibility": "personal", "awarded_users_count": 112, "is_published": true, "...": "…" },
      "created_at": "2026-07-14T09:30:12+02:00",
      "updated_at": "2026-07-14T09:30:12+02:00"
    }
  ]
}

Award fields

FieldMeaning
awarded_atThe day the award is for, with the Copenhagen offset: midnight for manual awards, the local time it was earned for automatic ones. Only the date is meaningful — see above.
is_manually_addedtrue for awards handed out in the CMS or through the API, false for awards earned through a trigger.
departmentThe department the award was given in, or null.
assigned_byThe CMS user who handed it out, the company's own user (type: customer) for awards given through the API, or null for automatic awards.
achievementThe achievement — embedded on /users/{id}/achievements and in the webhook.
userThe holder — embedded on /achievements/{id}/awards and in the webhook.