Skip to content

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/v1

Authentication ​

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"]
  }
}
StatusCodeMeaning
401unauthenticatedToken missing, invalid, revoked or expired
403forbiddenWorkspace API disabled, the user is no longer a member, or the user's role does not allow the action
403insufficient_scopeToken lacks the scope, or is read-only and you attempted a write
403feature_unavailableThe Overtime module is off for the workspace, or its plan does not include it
404not_foundRecord not found in this workspace
409invalid_stateThe record is in the wrong state, e.g. activating an event that is not a draft
422validation_failedInput 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.code is validation_failed, error.message is the messages joined into one sentence, and the per-field messages moved from errors to error.fields.
  • POST /schedule and DELETE /schedule response keys changed. Assigning used to return assigned, date and hours; it now returns dates_assigned, hours_written, skipped and warnings. Unassigning used to return unassigned and a single date; it now returns dates (a list) alongside hours_removed. Both also accept a from/to range now.
  • POST /schedule upserts. 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 forbidden unless the user's live role allows them (for example a member can no longer change the roster). GET /ot-events and GET /ot-events/{id}/coverage now follow the dashboard: members see only events they have access to, and coverage is staff-only. OT endpoints return feature_unavailable when the Overtime module is off.
  • Approving leave now updates the roster. POST /leaves with status: approved and PATCH /leaves/{id} to approved now 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 return feature_unavailable when the Leave management or Holiday calendar module is off.
  • Members can no longer read everyone's leave. GET /leaves returns a member only their own leave.
  • Agents and team can be managed over the API. New endpoints POST/PATCH/DELETE /agents… (agents:write) and GET/POST/PATCH/DELETE /team (new scopes team:read and team:write). Existing tokens do not have the team:* 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 /agents returns linked_user, can_edit and can_delete.
  • Work reports, tickets, the notepad and modules are now on the API (new scopes reports:write, tickets:read, tickets:write, notes:read, notes:write and workspace:write). Existing tokens do not have these scopes: add them in Settings β†’ API & MCP.
  • GET /work-reports/missed changed shape (breaking). It now returns from, to, missed (a flat list of one entry per agent, template and date), missed_count and scheduled_count. It defaults to the last 7 days, accepts template_id and include_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 unless include_silenced is passed; the admin tracker still counts them, so tracker totals equal missed_count with include_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.

ParameterValues
from / toYYYY-MM-DD. Both required together; overrides period
periodtoday, 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

QueryDescription
active_onlytrue (default) or false

Returns id, name, active, colour, holiday group, office days, off days and Slack member id, plus:

FieldDescription
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_editWhether you may change this agent (PATCH /agents/{id})
can_deleteWhether 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 / bodyDescription
confirm_delete_historytrue 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.

QueryDescription
dateRequired. YYYY-MM-DD
hour0–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

QueryDescription
from / to / periodDate range
agent_idLimit 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.

QueryDescription
from / to / periodDate range
min_agentsMinimum 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_roles in GET /team lists the roles you can give: an owner can give lead, manager, member; a lead manager, member; a manager member.
  • 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 forbidden with 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 / bodyDescription
confirm_delete_rostertrue 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

QueryDescription
from / to / periodDate range
statuspending, approved, rejected
agent_idLimit 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

QueryDescription
yearDefaults to this year
agent_idLimit 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

QueryDescription
yearDefaults 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

QueryDescription
from / to / periodDate range. Defaults to the current month
groupHoliday 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

QueryDescription
yearDefaults 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

QueryDescription
statusdraft, 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

QueryDescription
template_idLimit to one template
agent_idStaff only: limit to one agent
from / toWork date range
page / per_pagePagination (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_date defaults to today; it cannot be in the future or before the template's effective_from
  • editing an existing report for a past date needs allow_edit_past on the template
  • answer keys must be question ids of this template
  • required questions other than number need a non-empty answer
  • number answers must be numeric, email answers a valid email, date answers 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

QueryDescription
from / toDate range. Defaults to the last 7 days, today included
agent_idStaff only: limit to one agent
template_idLimit to one template
include_silenced1 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

QueryDescription
rangeweek (default) or month
sortscore (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}'

Built with ❀️ for support teams worldwide.