REST API
Authentication, conventions, rate limits and the main endpoints for notes, areas, tasks, uploads and chat.
Everything the SCAD Maps app does goes through its REST API, and the same API is available to your own scripts and tools, such as a dashboard on a wall screen or a script that files incidents from another system. This page describes how to authenticate, the conventions that apply everywhere, the limits and the main endpoints. Live updates arrive over a separate connection, described in Realtime events.
Base address and format#
- Every endpoint lives under
/apion the instance's own address, for examplehttps://your-instance/api/notes. Each instance has its own API, its own accounts and its own data. - Requests and responses are JSON (
Content-Type: application/json), except uploads, which send the raw file. - Times are ISO 8601 in UTC. Coordinates are decimal degrees, WGS 84, sent as
latitudeandlongitude. Bounding boxes usesouth,west,northandeast. - Identifiers are opaque strings. Do not parse them.
Authentication#
You sign in with an account and receive a short-lived access token. Send it on every request:
Authorization: Bearer <accessToken>
| Endpoint | Purpose |
|---|---|
POST /api/auth/login | Body { "identifier": "...", "password": "...", "remember": true }, where the identifier is the username or the email address. Returns the access token and the user. |
POST /api/auth/refresh | Exchanges the refresh token for a new access token and a new refresh token. Scripts send { "refreshToken": "..." } in the body; a browser sends its cookie plus an X-CSRF-Token header. |
POST /api/auth/logout | Ends the session. |
GET /api/auth/me | The signed-in user with access level, roles and flags. |
PUT /api/auth/me/settings | Saves the caller's own settings. |
POST /api/auth/me/account | Changes the caller's own email or password; needs the current password. |
GET /api/auth/config | Public. Whether self-signup is on, and the instance's default workspace layout. |
- The access token is valid for 15 minutes. Refresh a little before it expires, and always store the new refresh token: each one can be used once, and using an old one ends the whole session.
- Use a dedicated account for an integration, with the lowest access level that does the job. There are no separate API keys.
- Instances with single sign-on still accept local accounts for integrations; see Single sign-on.
- Accounts that are pending or rejected cannot sign in, and every request is checked against the account's current status and access level.
Conventions#
| Topic | Behaviour |
|---|---|
| Errors | A non-2xx status with a JSON body { "error": "message" }. The message is meant for a person, not for parsing. |
| Status codes | 400 invalid input, 401 missing or expired token, 403 not allowed, 404 not found or not visible to you, 409 conflict with the current state, 429 rate limit. |
| Visibility | Lists only contain what the caller may see. A record that exists but is hidden from the caller answers 404, not 403. |
| Pagination | Lists that can be large take page and pageSize (at most 500) and answer { "items": [...], "page": 1, "pageSize": 100 }. Loop until a page comes back shorter than pageSize. |
| Updates | PUT on a note replaces its editable fields; PATCH on a task changes only the fields you send. A field you leave out is kept, including attachments and checklists. |
| Text | Text is sanitised on the server. HTML is not interpreted. |
| Uploads | Files are uploaded first and linked to a record afterwards, by passing their identifiers when creating or updating it. |
Rate limits#
| Scope | Default |
|---|---|
All /api requests, per network address | 600 per minute |
| Creating, editing and deleting notes, areas and tasks, per user | 60 per minute. Admins are exempt. |
| Sending chat messages, per user | 40 per minute. Admins are exempt. |
| Sign-in, per account name and address | 8 attempts per 15 minutes |
A rate-limited request answers 429, and sign-in also sends a Retry-After header. Wait that long, or back off exponentially. Static tiles and images do not count towards the general limit. Operators can change the values; see Instance configuration.
Notes#
| Request | What it does |
|---|---|
GET /api/notes?south=&west=&north=&east= | Notes inside the box, paginated. archived=1 lists archived notes instead. |
GET /api/notes/:id | One note. |
GET /api/notes/:id/history | Its change history. |
POST /api/notes | Create. Body: content (1 to 500 characters), latitude and longitude together or neither (neither makes a personal note), optional guestVisible, checklist, attachmentIds. |
PUT /api/notes/:id | Edit. Author or Admin. |
PATCH /api/notes/:id/checklist | Tick one item with { "itemId": "...", "done": true }, or, for the author, replace the list with { "checklist": [...] }. |
POST /api/notes/:id/archive, /unarchive | Soft archive and restore. Author or Admin. |
DELETE /api/notes/:id | Delete. Author or Admin. |
Areas#
| Request | What it does |
|---|---|
GET /api/zones | All areas the caller may see. |
POST /api/zones | Create. Body: name (up to 120 characters), points (3 to 500 corners, outline must not cross itself), severity (CRITICAL, HIGH, MEDIUM or INFO), color, note, checklist, attachmentIds. Turning on geofence or capacity needs User Admin or Admin. |
PATCH /api/zones/:id, DELETE /api/zones/:id | Edit or delete. Creator or Admin. |
PATCH /api/zones/:id/checklist | The same tick-or-replace rules as for notes. |
Tasks and incidents#
Tasks and incidents share one set of endpoints. The kind field is TASK or INCIDENT.
| Request | What it does |
|---|---|
GET /api/tasks | List with filters: a bounding box, kind, status and priority (comma separated), tags (any of), assignedTo, roleId, includeClosed=1, includeFuture=1, archived=1 and page. |
GET /api/tasks/my | Tasks assigned to the caller. |
GET /api/tasks/unplaced | Tasks with no location yet. |
GET /api/tasks/options | The known choices: statuses, priorities, assignees, categories and tags. |
GET /api/tasks/:id, /history | One task, and its change history. |
POST /api/tasks | Create. Only title (1 to 140 characters) is required. Also accepts description (1,000), latitude with longitude, priority, status, assignees, dueAt, visibleFrom, occurredAt, slaMinutes, checklist, tags, markerLabel, markerColor and attachmentIds. |
PATCH /api/tasks/:id | Change status, assignment or any field you send. |
POST /api/tasks/:id/duplicate | Copy a task. |
POST /api/tasks/:id/ack, /unack | Acknowledge an incident, or withdraw the acknowledgement. |
POST /api/tasks/:id/archive, /unarchive | Archive and restore. |
DELETE /api/tasks/:id | Delete. Creator or Admin. |
Statuses are OPEN, IN_PROGRESS, BLOCKED, RESOLVED and CLOSED; priorities are CRITICAL, HIGH, MEDIUM and LOW. Closed tasks are left out of lists unless you ask for them.
Uploads#
POST /api/uploads takes the raw file as the request body. The Content-Type header decides how it is handled:
| Kind | Types | Limit |
|---|---|---|
| Image | WebP, JPEG, PNG | 2 MB, re-encoded to WebP |
| Document | PDF, plain text, CSV | 10 MB |
| Voice note | MP4/AAC, WebM, Ogg, MPEG; send X-Audio-Duration in milliseconds | 1 MB, removed after 7 days |
The answer describes a pending attachment with an identifier. Pass that identifier in attachmentIds when you create or update the note, area, task or chat message it belongs to. Pending files that are never linked are deleted after a few hours. GET /api/uploads/usage shows the caller's used space against the 50 MB quota. Guests cannot upload.
Chat#
| Request | What it does |
|---|---|
GET /api/chat/channels | Channels the caller belongs to, the roster and the caller's own id. |
GET /api/chat/channels/:token/messages | History, newest first, in pages. The token is a channel id, global for the instance-wide channel, or dmu_<userId> for a direct message. |
POST /api/chat/channels/:token/messages | Send. Body: body (up to 4,000 characters) and/or attachmentIds. A clientMsgId you generate makes a retry safe: the same id is never stored twice. |
POST /api/chat/channels/:token/read | Marks the channel read. |
PATCH /api/chat/messages/:id, DELETE | Edit or delete a message. Author, and moderators for delete. |
GET /api/chat/voice/:id | Streams a voice note. It needs the bearer token, and answers 410 once the audio is past its 7 days. |
Search and history#
GET /api/search?q=...&limit=10finds notes, tasks and areas the caller may see. The query needs at least 2 characters and the limit is at most 30. Guests get403.GET /api/me/historyreturns the caller's own timeline withlimit(at most 100) and abeforecursor taken from the previous answer'snextBefore.
Other endpoints#
GET /api/layerslists the map layers the caller may see, with bounds and order.GET /api/versionis public and reports the running build.- Administration lives under
/api/admin/...and needs an Admin or User Admin; some calls need a full Admin (see Roles and permissions matrix). This includes users and roles, layers, activity, export and the integrations. - Integrations expose their own endpoints for machinery positions, panoramas, weather radar and monday.com webhooks; they are documented on their integration pages.
A small example#
- Sign in with
POST /api/auth/loginand keep theaccessTokenand the refresh token. - Create an incident with
POST /api/tasks, body{ "kind": "INCIDENT", "title": "Blocked gate", "priority": "HIGH", "latitude": 52.37, "longitude": 4.9 }. - Everyone who has that part of the map open sees it appear within a moment, through the live connection.
- Later, close it with
PATCH /api/tasks/:idand{ "status": "RESOLVED" }.