Appearance
REST API Reference β
The same roster data the MCP server exposes, over plain HTTP. Use this for dashboards, scripts and integrations that are not AI agents.
Base URL β
https://app.manageroster.com/api/v1Authentication β
Bearer token in the Authorization header. Create one in Settings β API & MCP β see MCP server setup, the same tokens work for both.
bash
curl -s https://app.manageroster.com/api/v1/me \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Accept: application/json'Every token is bound to one workspace. There is no workspace parameter on any endpoint β the token determines it.
Responses β
Success responses wrap the payload in data:
json
{ "data": { "β¦": "β¦" } }Every error uses one envelope, with a stable code and a message written to be read by people and models:
json
{
"error": {
"code": "insufficient_scope",
"message": "This token is missing the required scope. Needs one of: leaves:read. It currently has: agents:read.",
"required_scopes": ["leaves:read"],
"token_scopes": ["agents:read"]
}
}| Status | Code | Meaning |
|---|---|---|
| 401 | unauthenticated | Token missing, invalid, revoked or expired |
| 403 | forbidden | Workspace API disabled, the user is no longer a member, or the user's role does not allow the action |
| 403 | insufficient_scope | Token lacks the scope, or is read-only and you attempted a write |
| 403 | feature_unavailable | The Overtime module is off for the workspace, or its plan does not include it |
| 404 | not_found | Record not found in this workspace |
| 409 | invalid_state | The record is in the wrong state, e.g. activating an event that is not a draft |
| 422 | validation_failed | Input rejected. fields maps each field name to its messages |
A validation error looks like:
json
{
"error": {
"code": "validation_failed",
"message": "end_hour must be greater than start_hour (it is exclusive).",
"fields": { "end_hour": ["end_hour must be greater than start_hour (it is exclusive)."] }
}
}Breaking changes in this release β
- Validation errors changed shape. They are no longer Laravel's
{"message": β¦, "errors": {β¦}}. They now use the standard envelope:error.codeisvalidation_failed,error.messageis the messages joined into one sentence, and the per-field messages moved fromerrorstoerror.fields. POST /scheduleandDELETE /scheduleresponse keys changed. Assigning used to returnassigned,dateandhours; it now returnsdates_assigned,hours_written,skippedandwarnings. Unassigning used to returnunassignedand a singledate; it now returnsdates(a list) alongsidehours_removed. Both also accept afrom/torange now.POST /scheduleupserts. Hours the agent is already rostered for are updated in place instead of being refused as a clash.- Roles are enforced on every call. Tokens of any role can carry any scope, but writes are refused with
forbiddenunless the user's live role allows them (for example a member can no longer change the roster).GET /ot-eventsandGET /ot-events/{id}/coveragenow follow the dashboard: members see only events they have access to, and coverage is staff-only. OT endpoints returnfeature_unavailablewhen the Overtime module is off. - Approving leave now updates the roster.
POST /leaveswithstatus: approvedandPATCH /leaves/{id}toapprovednow take the agent off the roster for those days (and restore them when un-approved, edited or deleted). Before, the API and MCP changed only the leave record. Leave and holiday endpoints also returnfeature_unavailablewhen the Leave management or Holiday calendar module is off. - Members can no longer read everyone's leave.
GET /leavesreturns a member only their own leave. - Agents and team can be managed over the API. New endpoints
POST/PATCH/DELETE /agentsβ¦(agents:write) andGET/POST/PATCH/DELETE /team(new scopesteam:readandteam:write). Existing tokens do not have theteam:*scopes: add them in Settings β API & MCP before using the team endpoints. - Rank rules apply everywhere, including the dashboard. Managers can only manage members, leads can manage managers and members, and agents linked to someone ranked at or above you cannot be edited, deactivated or deleted.
- The agent limit follows the workspace owner's plan, for both agents and team members added to the roster.
GET /agentsreturnslinked_user,can_editandcan_delete.- Work reports, tickets, the notepad and modules are now on the API (new scopes
reports:write,tickets:read,tickets:write,notes:read,notes:writeandworkspace:write). Existing tokens do not have these scopes: add them in Settings β API & MCP. GET /work-reports/missedchanged shape (breaking). It now returnsfrom,to,missed(a flat list of one entry per agent, template and date),missed_countandscheduled_count. It defaults to the last 7 days, acceptstemplate_idandinclude_silenced, is no longer staff-only (members see their own), and uses the same calculation as the admin tracker. Approved leave, holidays and days before a template's start date are excluded. Silenced days are left out unlessinclude_silencedis passed; the admin tracker still counts them, so tracker totals equalmissed_countwithinclude_silenced=1.- Dashboard work-report submits follow the API rules. Required text questions left empty, invalid email or date answers and future dates are rejected on every channel.
Date ranges β
Endpoints taking a range accept either explicit dates or a relative period.
| Parameter | Values |
|---|---|
from / to | YYYY-MM-DD. Both required together; overrides period |
period | today, next_week, this_month, next_month. Defaults to the current week |
Ranges longer than 366 days are truncated.
Endpoints β
GET /me β
Who the token acts as, its workspace, its effective scopes, and what the user can do right now. No scope required β use it to verify a token.
json
{
"data": {
"user": { "id": 4, "name": "Rahim Chowdhury", "email": "β¦", "role": "owner" },
"workspace": { "id": 2, "name": "xCloud Support", "slug": "β¦", "timezone": "Asia/Dhaka", "agent_count": 8, "features": {β¦} },
"linked_agent": { "id": 3, "name": "Nadia" },
"modules": { "ot_events": true },
"plan_includes_ot": true,
"token": { "name": "Claude Code β laptop", "scopes": ["schedule:read", "leaves:read"], "is_read_only": true, "last_used_at": "2026-09-06T10:22:41+00:00" },
"can": ["Read the roster, coverage gaps and who is available."]
}
}linked_agent is null when the user's account is not linked to an agent. can is a plain-language list derived from the user's live role, the token's scopes and the enabled modules; the MCP whoami tool returns the same object.
GET /workspace β
Scope: workspace:read
Workspace name, timezone, active agent count and enabled feature modules.
GET /agents β
Scope: agents:read
| Query | Description |
|---|---|
active_only | true (default) or false |
Returns id, name, active, colour, holiday group, office days, off days and Slack member id, plus:
| Field | Description |
|---|---|
linked_user | {id, name, role} of the account linked to this agent, or null. If several accounts share the agent, the highest-ranked one is shown |
can_edit | Whether you may change this agent (PATCH /agents/{id}) |
can_delete | Whether you may deactivate or delete this agent |
POST /agents β
Scope: agents:write β staff only
json
{ "name": "Rina", "color": "#22c55e", "holiday_group": "bd", "office_days": ["Monday", "Tuesday"], "off_days": ["Friday"], "slack_member_id": null }name and color are required. An empty holiday_group defaults to bd. Counts against the agent limit of the workspace owner's plan: when the limit is reached the call returns 422 validation_failed. Returns the new agent with 201.
PATCH /agents/{id} β
Scope: agents:write β staff only
Only the fields you pass change (name, color, holiday_group, office_days, off_days, slack_member_id). Changing holiday_group moves the agent's roster days to the new group's holidays. Returns the updated agent.
This endpoint also accepts active; a change of active is rank-checked exactly like PATCH /agents/{id}/active (MCP set_agent_active).
holiday_group must be a group code in this workspace, the default bd, or the agent's current group; any other code is refused with 422.
You cannot change an agent whose linked account is ranked at or above you (see Rank rules). You can always edit your own agent.
PATCH /agents/{id}/active β
Scope: agents:write β staff only
json
{ "active": false }active is required. Deactivates or reactivates the agent. Refused with 403 forbidden on your own agent and on agents linked to someone ranked at or above you.
DELETE /agents/{id} β
Scope: agents:write β staff only
| Query / body | Description |
|---|---|
confirm_delete_history | true to confirm. Without it the call is refused with 422 validation_failed and the message lists what would be deleted |
Deletes the agent and all its history (roster, leave, OT, work reports). Show the counts in the refusal to the user and repeat the call with confirm_delete_history=true only if they agree. Same refusals as deactivating: not your own agent, not one linked to someone ranked at or above you. Returns { "deleted": true, "id": β¦, "name": β¦ }.
GET /agents/available β
Scope: schedule:read
Agents not rostered and not on approved leave for a date.
| Query | Description |
|---|---|
date | Required. YYYY-MM-DD |
hour | 0β23. Omit to check the whole day |
Agents on a public holiday are included with on_public_holiday: true rather than filtered out β they are often available for overtime, and you want that context before asking.
GET /schedule β
Scope: schedule:read
| Query | Description |
|---|---|
from / to / period | Date range |
agent_id | Limit to one agent |
Consecutive hours for the same agent are collapsed into blocks. end_hour is exclusive.
json
{
"data": {
"from": "2026-09-07", "to": "2026-09-13",
"days": [
{ "date": "2026-09-07",
"blocks": [
{ "agent_id": 3, "agent_name": "Nadia", "start_hour": 9, "end_hour": 17, "hours": 8, "is_ot": false, "shift_type": "morning_day" }
] }
],
"total_assigned_hours": 112
}
}GET /schedule/coverage β
Scope: schedule:read
Uncovered hours as contiguous ranges, plus an overall coverage percentage.
| Query | Description |
|---|---|
from / to / period | Date range |
min_agents | Minimum agents for an hour to count as covered. Default 1 |
json
{
"data": {
"from": "2026-09-07", "to": "2026-09-13",
"min_agents": 1,
"gaps": [ { "date": "2026-09-08", "start_hour": 2, "end_hour": 6, "hours": 4, "assigned": 0, "required": 1 } ],
"gap_count": 1,
"uncovered_hours": 4,
"coverage_percent": 97.6
}
}POST /schedule β
Scope: schedule:write β staff only (owner, manager, lead)
Give one date, or a from/to range of at most 62 days.
json
{ "agent_id": 3, "date": "2026-09-08", "start_hour": 9, "end_hour": 17, "is_ot": false, "notes": null }json
{ "agent_id": 3, "from": "2026-09-08", "to": "2026-09-12", "start_hour": 9, "end_hour": 17 }Returns 201. Hours the agent is already rostered for are updated in place (upsert), not refused. Days the agent is on approved leave or has a public holiday are skipped. If every day is blocked, the call is refused with 422 and nothing is written.
json
{
"data": {
"agent_id": 3, "agent_name": "Nadia", "start_hour": 9, "end_hour": 17, "is_ot": false,
"dates_assigned": ["2026-09-08", "2026-09-09"],
"hours_written": 16,
"skipped": [ { "date": "2026-09-10", "reason": "Nadia is on approved annual leave from β¦ " } ],
"warnings": [ "Skipped 2026-09-10: Nadia is on approved annual leave β¦" ]
}
}For an overnight shift, make two calls β one ending at hour 24, one starting at hour 0 on the next date.
DELETE /schedule β
Scope: schedule:write β staff only
Same body as POST (date or from/to, agent_id, start_hour, end_hour) minus is_ot and notes.
json
{ "data": { "agent_id": 3, "dates": ["2026-09-08"], "hours_removed": 8 } }GET /agents/{id}/schedule-template β
Scope: schedule:read
An agent's default weekly pattern: seven entries with day_of_week (0 = Sunday), day, is_working, start_hour, end_hour and configured.
PUT /agents/{id}/schedule-template β
Scope: schedule:write β staff only
json
{ "confirm_regenerate": true, "days": [ { "day_of_week": 1, "start_hour": 9, "end_hour": 17, "is_working": true }, "β¦ all 7 days β¦" ] }days must hold exactly 7 entries. end_hour is 1β30; over 24 runs into the next day. This rebuilds the agent's roster from today through the next 183 days (about 6 months) and replaces manual and OT edits in that range, so confirm_regenerate: true is required β without it the call is refused with 422. Returns agent_id, the saved days and regenerated (from, to and the row count). After that, a daily job adds each new day at the far end of the window from the template without touching existing entries, so the roster never runs out.
Team β
Manage who is in the workspace and what role they have. Needs team:read / team:write. All team endpoints are staff only (owner, manager, lead): a member's token gets 403 forbidden.
Rank rules β
Roles rank owner > lead > manager > member.
- You can add, re-role, link and remove only people ranked below you, and give only roles below your own.
assignable_rolesinGET /teamlists the roles you can give: an owner can givelead,manager,member; a leadmanager,member; a managermember. - Nobody can change the owner's role or remove the owner.
- You cannot change your own role or remove yourself.
- You can link or unlink an agent for yourself as well as for people ranked below you.
- Agents follow the same rule: you cannot edit, deactivate or delete an agent linked to someone ranked at or above you. You cannot deactivate or delete your own agent.
- Rank refusals return
403 forbiddenwith a message naming the rule that applied.
GET /team β
Scope: team:read β staff only
json
{
"data": {
"members": [
{ "id": 4, "name": "Rahim", "email": "β¦", "role": "owner", "is_owner": true,
"agent": null, "can_change_role": false, "can_link_agent": true, "can_remove": false }
],
"assignable_roles": ["lead", "manager", "member"]
}
}Members are ordered by rank, highest first. agent is {id, name, color} or null. The can_* flags say what you may do to that person right now.
POST /team β
Scope: team:write β staff only
json
{ "name": "Nadia", "email": "[email protected]", "role": "member", "add_to_roster": true, "agent_color": "#22c55e" }name and email are required; role is lead, manager or member (default member) and must be below your own. A new email gets an account and an invite email; an existing account is attached to the workspace. A member always gets a roster agent; a lead or manager only with add_to_roster: true. Optional schedule_days is exactly 7 entries of {day_of_week 0-6, start_hour 0-23, end_hour 1-30, is_working}.
Returns 201 with user_id, name, email, role, invited, agent_id, added_to_roster (an agent was actually created) and roster_requested. Refused with 422 if the email is already in the workspace or the owner's plan has no agent room left for the roster agent.
PATCH /team/{user_id} β
Scope: team:write β staff only
json
{ "role": "manager", "agent_id": 12 }Pass role, agent_id, or both. agent_id: null unlinks. When both are passed they apply atomically: if the link is refused (for example the agent is already linked to someone else, 422), the role change is rolled back too. Returns the updated member as in GET /team.
DELETE /team/{user_id} β
Scope: team:write β staff only
| Query / body | Description |
|---|---|
confirm_delete_roster | true to confirm. Needed only when the person has a roster agent |
Removes the person from the workspace. Their roster agent in this workspace is deleted with all its history, so without confirm_delete_roster the call is refused with 422 and the counts of what would go. Returns { "removed": true, "user_id": β¦, "roster_removed": true|false }.
If another member of this workspace is linked to the same agent, the agent is kept: the person is only unlinked and removed, no confirmation is needed, and roster_removed is false.
Leave β
Needs the Leave management module β when it is off for the workspace, or not on the plan, these endpoints return feature_unavailable. Staff are owner, manager and lead.
Approval updates the roster. Approving leave (on create or via PATCH) removes the agent from the roster for those days; un-approving, editing the dates of approved leave, or deleting it puts them back. Previously the API and MCP changed the status but left the roster alone.
GET /leaves β
Scope: leaves:read
| Query | Description |
|---|---|
from / to / period | Date range |
status | pending, approved, rejected |
agent_id | Limit to one agent (staff only) |
Matches on overlap, so leave starting before the range and running into it is included. Staff see every agent; members see only their own leave.
POST /leaves β
Scope: leaves:write
json
{ "agent_id": 3, "type": "annual", "start_date": "2026-09-14", "end_date": "2026-09-18", "notes": null }type is one of annual, sick, casual, maternity. status defaults to pending; pass approved to approve on creation (the agent comes off the roster).
Staff only β a member's token gets 403 forbidden (members use POST /me/leaves). Refuses with 422 on overlapping leave. Returns a warnings array β not an error β when the agent is already rostered during the requested dates, since those shifts need reassigning.
PATCH /leaves/{id} β
Scope: leaves:write β staff only
json
{ "status": "approved" }Only the fields you pass change: agent_id, type, start_date, end_date, status, notes. If the leave is approved, the old days go back on the roster and the new days come off.
DELETE /leaves/{id} β
Scope: leaves:write β staff only
Deletes the leave. If it was approved, the agent goes back on the roster for those days.
GET /leave-balances β
Scope: leaves:read β staff only
| Query | Description |
|---|---|
year | Defaults to this year |
agent_id | Limit to one agent |
Each agent's total, used (approved) and remaining days per leave type.
PUT /agents/{id}/leave-balance β
Scope: leaves:write β staff only
json
{ "year": 2026, "casual_total": 10, "annual_total": 20, "sick_total": 10, "maternity_total": 0 }Sets the agent's yearly allowance for each type. All five fields are required. year must be between 2000 and the current year plus 5.
Own leave β
For the connected user's own linked agent. Needs the Leave management module and a user linked to an agent.
GET /me/leaves β
Scope: leaves:read
| Query | Description |
|---|---|
year | Defaults to this year |
Your leave for the year (all statuses) and your remaining balance per type.
POST /me/leaves β
Scope: leaves:write
json
{ "type": "annual", "start_date": "2026-09-14", "end_date": "2026-09-14", "notes": "Family trip" }Always created as pending for a manager to approve; there is no status or agent_id field. Must start today or later. Refuses with 422 if you already have leave on those days. Returns 201.
DELETE /me/leaves/{id} β
Scope: leaves:write
Withdraws one of your own requests while it is still pending. Approved or rejected leave, and other people's leave, is refused.
Holidays β
Needs the Holiday calendar module β when it is off, these endpoints return feature_unavailable. Reads are open to every role; writes are staff only (403 forbidden for members). Holiday writes update the roster: a new holiday takes the affected agents off that day, and moving or deleting one puts them back.
GET /holidays β
Scope: holidays:read
| Query | Description |
|---|---|
from / to / period | Date range. Defaults to the current month |
group | Holiday group code, e.g. BD, PH |
Each holiday includes affects_agents β the agents in that holiday group (every agent for group: "all") β because groups are per-agent and one workspace can run several national calendars.
POST /holidays Β· PATCH /holidays/{id} Β· DELETE /holidays/{id} β
Scope: holidays:write β staff only
json
{ "name": "Eid al-Fitr", "date": "2026-03-20", "group": "BD", "is_recurring": false, "color": "#16a34a", "notes": null }name, date and group (all or a holiday group code) are required on create; PATCH changes only the fields you pass.
GET /holiday-groups β
Scope: holidays:read
Holiday groups (national calendars) with how many holidays and agents each has.
POST /holiday-groups Β· PATCH /holiday-groups/{code} Β· DELETE /holiday-groups/{code} β
Scope: holidays:write β staff only
json
{ "code": "bd", "name": "Bangladesh", "flag_emoji": "π§π©", "color": "#16a34a", "description": null }code (letters, numbers, - or _) and name are required on create; the code cannot be changed later. DELETE is refused while the group still has holidays.
GET /holiday-presets/{code} β
Scope: holidays:read β staff only
| Query | Description |
|---|---|
year | Defaults to this year |
The built-in public holidays for the group's country, each marked if already added.
POST /holiday-groups/{code}/import β
Scope: holidays:write β staff only
json
{ "year": 2026, "dates": ["2026-03-20"] }Imports the built-in holidays for the year; dates is optional and limits the import. Holidays already added are skipped. The response lists imported, skipped and warnings.
Instead of {year, dates?} you can send an explicit list:
json
{ "holidays": [{ "name": "Founders Day", "date": "2026-03-20", "is_recurring": false, "color": "#ef4444", "notes": "Optional" }] }Each entry needs name and date; is_recurring, color and notes are optional.
Overtime β
All OT endpoints return feature_unavailable (403) when the Overtime module is off for the workspace or the plan does not include it. "Staff" means owner, manager and lead; a "member" acts through their linked agent.
An event is draft β active β closed. An OT event body is {name, description, start_date, end_date, max_slots_per_day, mandatory_dates}; max_slots_per_day (1β24, default 8) and mandatory_dates are advisory guidelines β breaching them produces a warning, never a refusal. Events render as {id, name, description, status, start_date, end_date, max_slots_per_day, mandatory_dates}.
GET /ot-events β
Scope: ot:read
| Query | Description |
|---|---|
status | draft, active, closed |
Staff see every event. A member sees only events their linked agent has access to (an empty list if the account has no linked agent). Each row: id, name, status, start_date, end_date, submissions_count.
GET /ot-events/{id} β
Scope: ot:read
One event. Staff also get access: every agent with access and their submission_status. A member gets just event, and only if they have access (otherwise 403 forbidden).
GET /ot-events/{id}/coverage β
Scope: ot:read β staff only
Slot-by-slot volunteer coverage: 24 one-hour slots per event day (slot_index is the hour), each with volunteers and count, plus uncovered_slots and max_slots_per_day_guideline β the advisory per-agent cap (default 8), not the number of slots.
POST /ot-events β
Scope: ot:write β staff only
Body: name, start_date, end_date (required), description, max_slots_per_day, mandatory_dates. Returns 201 with the event as a draft.
PATCH /ot-events/{id} β
Scope: ot:write β staff only
Partial update; only the fields you send change. 409 invalid_state once the event is closed.
DELETE /ot-events/{id} β
Scope: ot:write β staff only
Draft events only (409 invalid_state otherwise β close the event instead). Returns { "deleted": true, "id": β¦ }.
POST /ot-events/{id}/activate Β· POST /ot-events/{id}/close β
Scope: ot:write β staff only
Activate a draft so agents with access can submit; close an active event so submissions stop (reviews can continue). 409 invalid_state from any other status. Both return the event.
POST /ot-events/{id}/access Β· DELETE /ot-events/{id}/access β
Scope: ot:write β staff only
json
{ "agent_ids": [3, 5] }Grant returns {event_id, granted, already_had_access}; it is safe to repeat. Revoke returns {event_id, revoked, had_no_access} and keeps the agent's existing submission.
GET /ot-events/{id}/submissions β
Scope: ot:read β staff only
Every submission for the event (id, agent_id, agent_name, status, submitted_at, reviewed_at, admin_notes, hours_by_date, hour_count) plus not_submitted: agents with access who have not submitted.
GET /ot-events/{id}/submissions/{submission} β
Scope: ot:read β staff only
One submission in detail.
POST /ot-events/{id}/submissions/{submission}/approve β
Scope: ot:write β staff only
Optional body { "notes": "β¦" }. Works on submitted or previously rejected submissions (409 invalid_state otherwise) and writes the agent's hours to the roster as OT. Returns the submission, schedule_hours_written (hours newly added to the roster) and schedule_hours_already_rostered (hours the agent already had, which stay as regular hours).
POST /ot-events/{id}/submissions/{submission}/reject β
Scope: ot:write β staff only
Optional body { "notes": "β¦" } (max 500 characters). Works on submitted or approved submissions; if it was approved, its OT hours are removed from the roster. Returns the submission and schedule_hours_removed.
DELETE /ot-events/{id}/submissions/{submission} β
Scope: ot:write β owner only
Deletes the submission, and its roster hours if it was approved, so the agent can submit again. Returns {deleted, submission_id, agent_id}.
GET /me/ot-events β
Scope: ot:read β needs a linked agent
The events the connected user's agent has access to, each with my_status (not_started, draft, submitted, β¦). Returns agent and ot_events. Without a linked agent: 403 forbidden.
GET /me/ot-events/{id}/availability β
Scope: ot:read β needs a linked agent with access to the event
Returns event and the user's own submission (or null).
PUT /me/ot-events/{id}/availability β
Scope: ot:write β needs a linked agent with access to the event
json
{ "slots": [ { "date": "2026-09-09", "hours": [8, 9, 10, 11] } ], "submit": true }hours are 0β23 (9 = 09:00β10:00). submit: true sends the selection for review, false saves a draft. It replaces the previous selection and is locked once approved. Returns the submission and warnings β guideline breaches such as exceeding max_slots_per_day are warnings, not errors. 409 invalid_state if the event is not active.
Work reports β
Needs the Work reports module on and in the plan; otherwise these endpoints return feature_unavailable. Dates are YYYY-MM-DD in the workspace timezone. Staff (owner, lead, manager) see everything; a member sees the active templates assigned to their linked agent and their own submissions and missed reports.
GET /work-reports/templates Β· GET /work-reports/templates/{id} β
Scope: reports:read
Staff get every template, including inactive ones. Members get the active templates assigned to them. The single-template read includes questions (answers are keyed by question id); for staff it also includes agents and slack (the Slack setting).
POST /work-reports/templates Β· PATCH /work-reports/templates/{id} β
Scope: reports:write β staff only
json
{
"name": "End of shift",
"effective_from": "2026-06-01",
"allow_edit_past": false,
"agent_ids": [3, 4],
"questions": [
{ "id": null, "section": "General", "label": "Tickets closed", "type": "number", "required": true }
]
}Fields: name (required on create), description, icon (an emoji), color, is_active, allow_edit_past, effective_from, agent_ids, slack_enabled, slack_channel_id, slack_channel_name and questions. Each question takes id, label, type, section, required and placeholder; type is one of number, text, textarea, email, date. effective_from is the first day that can count as missed; it defaults to today in the workspace timezone. Every agent_ids entry must be an agent in this workspace. PATCH is partial: fields you leave out stay unchanged.
questions replaces the list. On PATCH, send every question you want to keep, with its id from GET /work-reports/templates/{id}. Existing questions you leave out are deleted, except questions that already have answers, which are kept. Omit questions entirely to leave them unchanged.
PATCH /work-reports/templates/{id}/active β
Scope: reports:write β staff only
Body { "active": true }. Inactive templates are not shown to agents and never count as missed.
DELETE /work-reports/templates/{id} β
Scope: reports:write β staff only
Deletes the template and all its submissions. If it has submissions the call is refused with their count; show it to the user and repeat with ?confirm_delete_submissions=1 only if they agree. Returns { "deleted": true, "id": β¦, "name": β¦, "submissions_deleted": n }.
GET /work-reports/submissions Β· GET /work-reports/submissions/{id} β
Scope: reports:read
| Query | Description |
|---|---|
template_id | Limit to one template |
agent_id | Staff only: limit to one agent |
from / to | Work date range |
page / per_page | Pagination (newest first) |
Returns data and meta. Each submission has work_date, agent, template, submitted_by, submitted_at, notes and answers (question_id, label, type, value).
POST /work-reports/submissions β
Scope: reports:write β needs your account linked to an agent
json
{ "template_id": 7, "work_date": "2026-06-09", "notes": "Quiet shift", "answers": { "41": "12", "42": "Escalated two chats" } }You submit for your own linked agent only. Submitting again for the same date updates the existing report. Rules, all enforced on the server:
- the template must be active and assigned to your agent
work_datedefaults to today; it cannot be in the future or before the template'seffective_from- editing an existing report for a past date needs
allow_edit_paston the template - answer keys must be question ids of this template
- required questions other than
numberneed a non-empty answer numberanswers must be numeric,emailanswers a valid email,dateanswers a valid date- questions you do not answer are stored as
0
Failures return 422 validation_failed with the field in error.fields. A successful submit posts to Slack (a Slack failure never fails the request) and clears that day's missed-report notice. Returns 201 with the submission.
GET /work-reports/missed β
Scope: reports:read
| Query | Description |
|---|---|
from / to | Date range. Defaults to the last 7 days, today included |
agent_id | Staff only: limit to one agent |
template_id | Limit to one template |
include_silenced | 1 to also list silenced days |
Staff see every agent; a member sees only their own (and needs a linked agent). A missed report is a working day, after the shift ended, with no submission for an active template that applies to the agent. Holidays, approved leave and days before the template's effective_from are excluded, and days not rostered are not counted. Silenced days are left out unless include_silenced=1 is passed. This is the same calculation the admin Missed Reports tracker uses, but the tracker still counts silenced days, so its totals equal missed_count with include_silenced=1.
json
{
"data": {
"from": "2026-06-04",
"to": "2026-06-10",
"missed": [
{ "date": "2026-06-04", "agent_id": 3, "agent_name": "Aisha", "template_id": 7, "template_name": "End of shift", "notified": false, "silenced": false, "dismissed": false }
],
"missed_count": 1,
"scheduled_count": 5
}
}scheduled_count is the number of agent-day-template slots that were due in the range, so missed_count / scheduled_count is the miss rate.
PATCH /work-reports/notices/dismiss β
Scope: reports:write β needs a linked agent
Body { "notification_id": 12 } dismisses one of your own missed-report notices; omit it to dismiss all your open ones.
POST /work-reports/missed/notify β
Scope: reports:write β staff only
Body { "agent_ids": [3, 4], "from": "2026-06-04", "to": "2026-06-09" }. Sends missed-report reminders for those agents and dates, so it notifies the agents. Returns { "agents": n, "reminders_dispatched": n }.
PATCH /work-reports/missed/silence Β· PATCH /work-reports/missed/unsilence β
Scope: reports:write β owner and lead only
Body { "agent_id": 3, "date": "2026-06-04", "template_id": 7 }. A silenced miss stops reminders and the banner and is left out of GET /work-reports/missed unless include_silenced is true; the admin tracker still counts it. Tracker totals equal missed_count with include_silenced=1. template_id is optional: omit it to silence (or restore) every template for that agent and date. The dashboard always works per agent and date, so a dashboard silence covers every template.
PATCH /work-reports/missed/banner β
Scope: reports:write β owner and lead only
Body { "enabled": false }. Turns the missed-report banner shown to agents on or off for the workspace.
GET /work-reports/leaderboard β
Scope: reports:read β staff only
| Query | Description |
|---|---|
range | week (default) or month |
sort | score (default), tickets, chats, reviews, tasks or rate |
Tickets β
Support tickets for the workspace. Staff only.
GET /tickets Β· GET /tickets/{id} β
Scope: tickets:read β staff only
GET /tickets takes an optional status (open, in_progress, resolved, closed) and lists newest first. GET /tickets/{id} includes the replies.
POST /tickets β
Scope: tickets:write β staff only
json
{ "subject": "Slack alerts stopped", "description": "Missed-report reminders no longer post.", "priority": "high" }priority is low, normal, high or urgent; description is at most 5000 characters. Returns 201 with the ticket.
POST /tickets/{id}/replies β
Scope: tickets:write β staff only
Body { "message": "β¦" }. Returns 201 with the reply.
Notepad β
Your own dashboard note for the token's workspace. It is private to you.
GET /me/note β
Scope: notes:read
Returns { "data": { "note": "β¦" } }.
PUT /me/note β
Scope: notes:write
Body { "note": "β¦" }. The note key is required, at most 5000 characters; send null or an empty string to clear it. This replaces the whole note, so read it first if you want to append.
Modules β
GET /workspace/modules β
Scope: workspace:read
Each module has key, name, description, enabled and plan_locked.
PUT /workspace/modules/{key} β
Scope: workspace:write β staff only
Body { "enabled": true }. Turning a module off makes its tools and endpoints return feature_unavailable (for everyone, including you) until it is turned back on. No data is deleted. Plan-locked modules cannot be changed (422). Use the keys from GET /workspace/modules.
Rate limits β
The default API throttle is 60 requests per minute per token. Exceeding it returns 429 with a Retry-After header.
Examples β
Who is on leave next week
bash
curl -s 'https://app.manageroster.com/api/v1/leaves?period=next_week&status=approved' \
-H 'Authorization: Bearer YOUR_TOKEN'Which slots are empty this week
bash
curl -s 'https://app.manageroster.com/api/v1/schedule/coverage?period=this_week' \
-H 'Authorization: Bearer YOUR_TOKEN'Assign a night shift crossing midnight
bash
curl -s -X POST https://app.manageroster.com/api/v1/schedule \
-H 'Authorization: Bearer YOUR_TOKEN' -H 'Content-Type: application/json' \
-d '{"agent_id":3,"date":"2026-09-08","start_hour":22,"end_hour":24}'
curl -s -X POST https://app.manageroster.com/api/v1/schedule \
-H 'Authorization: Bearer YOUR_TOKEN' -H 'Content-Type: application/json' \
-d '{"agent_id":3,"date":"2026-09-09","start_hour":0,"end_hour":6}'Related β
- MCP server setup β connect an AI client instead
- MCP tool reference β the tool equivalents of these endpoints
- Connection scopes β what each scope permits