Second half of the recognitions API guide for the UI. The first half covers drafts, publish, visibility, delete, and read rules.
Comments
Create
POST /Recognitions/:id/comments
{
"body": "Well done",
"decorations": [
{ "kind": "badge", "reactionOptionId": "<id>" },
{ "kind": "emotion", "reactionOptionId": "<id>" },
{ "kind": "gif", "gifUrl": "https://media.example/high-five.gif" }
]
}
The recognition must be published, public, and unlocked. The commenter must have the feature on and must be inside the company's comment hours, evaluated in the commenter's time zone. Hours are whole hours, start inclusive, end exclusive. Empty hours mean any time.
The comment needs text or at least one decoration. kind is badge, emotion, or gif.
badgeandemotionsendreactionOptionIdof an active option whose category matches, either a platform option or one owned by the company.- When
gifModeisopen(the default), a GIF sendsgifUrl, anhttpsURL with no spaces, quotes, or markup. - When
gifModeiscatalog, a GIF sendsgifAssetIdof an active GIF in the company. A raw URL is rejected.
The response is the comment plus a decorations array in the same shape as thread.
Default settings create the comment as held with holdUntil about 10 seconds out. When the hold ends, auto approval (the default) moves it to approved. manual approval moves it to pending. unsendSeconds: 0 skips held and creates the comment already approved or pending, with holdUntil: null. The UI should keep the unsend control up until holdUntil, then refresh the thread.
Unsend
POST /RecognitionComments/:id/unsend
No body. Only the author, and only while status is held and holdUntil is still in the future. The response is the comment with status: "withdrawn". Withdrawn comments stay visible to the author only.
Moderation
GET /RecognitionComments/moderation-queue
No filter. Returns up to 200 items, oldest first:
[
{ "comment": { "id": "...", "status": "pending", "body": "...", "recognitionId": "..." }, "recognition": { "id": "...", "visibility": "public", "message": "..." } }
]
The queue is pending comments the caller can review: an admin, or a covering moderator while the recognition is public. Pending comments on a private recognition are admin-only. The comment's decorations are not in this payload. Load thread for those.
POST /RecognitionComments/:id/approve and POST /RecognitionComments/:id/reject take no body. The comment must be pending. The response is the comment with reviewedById, reviewedOn, and status of approved or rejected.
Catalogs
Reactions
GET /ReactionOptions/available
Optional query category=badge or category=emotion. Returns active platform reactions plus the company's active reactions, platform rows first. This is the picker.
Each option: id, owner (platform or company), category (badge or emotion), name, symbol, imageUrl, active.
GET /ReactionOptions is the same catalog. Employees see active rows only. Admins and support also see inactive company rows, so they can turn one back on. Platform rows are read-only.
Admin create:
{ "category": "badge", "name": "Star", "symbol": "star", "imageUrl": "https://..." }
imageUrl is optional and must be https when present. The server sets owner: "company" and companyId. PATCH /ReactionOptions/:id can change name, symbol, imageUrl, and active. It cannot change owner, companyId, or category, and it cannot change a platform row. There is no delete. Set active to false.
GIFs
GET /GifAssets?filter={"where":{"active":true}}
Employees already only receive active rows. Fields: id, companyId, name, symbol, imageUrl, url, active.
Admin create and patch use the same fields. name is required, at most 64 characters. url is required and must be https. imageUrl is optional and must be https. There is no delete. Set active to false.
Occasions
GET /RecognitionOccasions
Read-only. Fields: id, code, name, forcePrivate, active. Seeded codes are birthday and anniversary, both forcePrivate: true. Show these in the admin compose form and send the chosen id as occasionId.
Settings
GET /RecognitionSettings is open to any signed-in user. PUT /RecognitionSettings is admin or support. The body is the settings object, not wrapped in another field.
{
"gifMode": "open",
"commentApproval": "auto",
"commentStartHour": null,
"commentEndHour": null,
"unsendSeconds": 10
}
| Field | Values |
|---|---|
gifMode | open (any https GIF URL) or catalog (company GIFs only) |
commentApproval | auto or manual |
commentStartHour | null, or a whole hour 0 through 23. null only together with commentEndHour |
commentEndHour | null, or a whole hour 1 through 24, later than the start. The end hour is exclusive |
unsendSeconds | whole number 0 through 300. Default 10 |
PUT merges onto the current settings and returns the stored object. Omitted fields keep their current value. Both hour fields must be set or both empty.
Who can use recognitions
RecognitionAccess is admin-only: find, findById, count, create, PATCH /:id, DELETE /:id.
{ "targetType": "company", "enabled": true }
{ "targetType": "team", "targetId": "<OrgTeam id>", "enabled": false }
targetType is company, subsidiary, or team. A company row has no targetId. A subsidiary or team row requires targetId. One row per scope. A second row for the same scope is 409 ACCESS-DUPLICATE. PATCH changes enabled or the target. companyId cannot change.
Resolution for a person, first match wins:
- Any of their teams has a row: the feature is on if any of those team rows is enabled.
- Otherwise their subsidiary row, if one exists.
- Otherwise the company row. If more than one company row exists, all of them must be enabled.
- No rows means off.
enabled: false forces that scope off. DELETE removes the override so the parent scope applies again. It is a real delete, not a soft delete. The UI should send a real subsidiary or team id. The API checks the shape of the scope, not that the id belongs to the company.
Moderators
CommentModerator is admin-only.
{ "userId": "<user id>", "scopeType": "company" }
{ "userId": "<user id>", "scopeType": "subsidiary", "scopeId": "<subsidiary id>" }
scopeType is company, subsidiary, or team. Company has no scopeId. Subsidiary and team require one. The user must belong to the company. The server sets status: "active".
DELETE /CommentModerators/:id returns { "count": 1 } and hides the grant from find. The same person and scope can be granted again afterward. A second active grant for the same person and scope is 422.
A moderator covers the recipient when the scope is the whole company, the recipient's subsidiary, or an active team the recipient belongs to. That lets them publish, read public recognitions in scope, and review pending comments on public recognitions. It does not let them delete a recognition or change its visibility.