Monotree Open API Reference (v0.11.0 Beta) Base URL: https://customer.monotree.com/api/open/v1 (Replace "customer" with your customer's Monotree subdomain.) Monotree is a workplace communication and intranet platform. The Open API lets external systems read and write data, react to events via outgoing webhooks, and push messages into Monotree via incoming webhooks. ======================================== AUTHENTICATION ======================================== All API requests require a Bearer token in the Authorization header (except incoming webhooks which use a token in the URL). curl https://customer.monotree.com/api/open/v1/walls \ -H "Authorization: Bearer mono_your_token_here" \ -H "Content-Type: application/json" Tokens are prefixed with "mono_" and have scoped permissions: - read:posts, write:posts - read:announcements, write:announcements - read:users - read:groups - read:walls - read:events, write:events - read:calendar_entries, write:calendar_entries - read:stats Tokens can optionally be restricted to a specific IP address and can have an expiration date. Content created via the API is attributed to either the token creator or a system user, configured when the token is created. ======================================== RESOURCES ======================================== ---------------------------------------- WALLS (scope: read:walls) ---------------------------------------- Walls are content spaces where posts are published. Each wall can be scoped to specific groups (departments/teams). GET /walls — List all walls (paginated) GET /walls/{id} — Get a single wall Response: { "data": { "id": 1, "name": "General", "groups": [ { "id": 1, "name": "Kitchen Staff", "type": "department", "created_at": "2026-01-15T10:00:00+00:00" } ], "created_at": "2026-01-15T10:00:00+00:00" } } ---------------------------------------- POSTS (scope: read:posts, write:posts) ---------------------------------------- Posts are content items on a wall. They are nested under walls in the URL. GET /walls/{wall_id}/posts — List posts on a wall (paginated) GET /walls/{wall_id}/posts/{id} — Get a single post POST /walls/{wall_id}/posts — Create a post PUT /walls/{wall_id}/posts/{id} — Update a post DELETE /walls/{wall_id}/posts/{id} — Delete a post (returns 204) Create request body: { "body": "Hello from the API!", "media_ids": [123, 124] // optional — IDs from POST /media. Requires the write:media scope. } Update request body: { "body": "Updated body" } Notes on media_ids: - Upload each file via POST /media first (see Media section) to get an ID. - All attached media must be of the same type (image / video / document). Mixed → 422. - Maximum 10 media items per post. - Attaching media requires the write:media scope in addition to write:posts. Response: { "data": { "id": 123, "body": "Hello from the API!", "wall_id": 1, "wall_name": "General", "author": { "id": 1, "name": "John Doe", "email": "john@example.com", "type": "user", "groups": [ { "id": 1, "name": "Kitchen Staff", "type": "department", "created_at": "2026-01-15T10:00:00+00:00" } ], "hired_at": "2025-06-01", "is_away": false, "last_active_on": "2026-03-22", "registered_at": "2025-06-01T09:00:00+00:00", "created_at": "2025-06-01T09:00:00+00:00" }, "created_at": "2026-03-22T10:00:00+00:00", "updated_at": "2026-03-22T10:00:00+00:00" } } ---------------------------------------- COMMENTS (scope: read:comments, write:comments) ---------------------------------------- Comments are replies on posts. They are nested under posts in the URL. GET /posts/{post_id}/comments — List comments on a post (paginated) GET /posts/{post_id}/comments/{id} — Get a single comment POST /posts/{post_id}/comments — Add a comment to a post PUT /posts/{post_id}/comments/{id} — Update a comment (author only, within 30 minutes) DELETE /posts/{post_id}/comments/{id} — Delete a comment (returns 204) Create/update request body: { "body": "Thanks for the update!" } Response: { "data": { "id": 789, "body": "Thanks for the update!", "post_id": 123, "wall_id": 1, "author": { "id": 42, "name": "Jane Doe", "email": "jane@example.com", "type": "user", "groups": [ { "id": 1, "name": "Kitchen Staff", "type": "department", "created_at": "2026-01-15T10:00:00+00:00" } ], "hired_at": "2025-06-01", "is_away": false, "last_active_on": "2026-03-22", "registered_at": "2025-06-01T09:00:00+00:00", "created_at": "2025-06-01T09:00:00+00:00" }, "created_at": "2026-03-22T10:00:00+00:00", "updated_at": "2026-03-22T10:00:00+00:00" } } Updates are only permitted by the comment author within 30 minutes of creation. ---------------------------------------- MEDIA (scope: write:media) ---------------------------------------- Upload files (images, videos, documents) to attach to wall posts. Two-step flow: 1. POST /media with the file → returns a media object with an id. 2. Reference the id in the media_ids array on POST /walls/{wall_id}/posts. POST /media — Upload a file (multipart/form-data) GET /media/{id} — Get a media object by id Upload request (multipart/form-data): media: (required) type: image|video|document (required) Size limit: governed by the server's media config (typically around 100 MB for video, smaller for images). Response: { "data": { "id": 42, "type": "image", "mime_type": "image/jpeg", "filename": "photo.jpg", "size": 123456, "url": "https://cdn.monotree.dk/.../photo.jpg", "width": 1920, "height": 1080, "created_at": "2026-04-24T10:00:00+00:00" } } curl example: curl -X POST https://customer.monotree.com/api/open/v1/media \ -H "Authorization: Bearer mono_..." \ -F "media=@./photo.jpg" \ -F "type=image" Then attach on post creation: curl -X POST https://customer.monotree.com/api/open/v1/walls/1/posts \ -H "Authorization: Bearer mono_..." \ -H "Content-Type: application/json" \ -d '{"body": "Look at this!", "media_ids": [42]}' ---------------------------------------- ANNOUNCEMENTS (scope: read:announcements, write:announcements) ---------------------------------------- Announcements are company-wide messages that can be targeted to specific groups. They are automatically published on creation. GET /announcements — List published announcements (paginated) GET /announcements/{id} — Get a single announcement POST /announcements — Create and publish an announcement PUT /announcements/{id} — Update an announcement DELETE /announcements/{id} — Delete an announcement (returns 204) Create request body: { "title": "Office closed on Friday", "body": "Due to maintenance, the office will be closed this Friday.", "subtitle": "Facility update", "groups": [1, 3] } Update request body (all fields optional): { "title": "Updated title", "body": "Updated body" } Response: { "data": { "id": 456, "title": "Office closed on Friday", "subtitle": "Facility update", "body": "Due to maintenance, the office will be closed this Friday.", "author": { "id": 1, "name": "John Doe", "email": "john@example.com", "type": "user", "groups": [], "hired_at": "2025-06-01", "is_away": false, "last_active_on": "2026-03-22", "registered_at": "2025-06-01T09:00:00+00:00", "created_at": "2025-06-01T09:00:00+00:00" }, "groups": [ { "id": 1, "name": "All Staff", "type": "custom", "created_at": "2026-01-15T10:00:00+00:00" } ], "is_published": true, "is_commentable": true, "is_confirmable": false, "is_pinned": false, "comments_count": 0, "completions_count": 0, "confirmations_count": 0, "starts_at": "2026-03-22", "published_at": "2026-03-22T10:00:00+00:00", "created_at": "2026-03-22T10:00:00+00:00", "updated_at": "2026-03-22T10:00:00+00:00" } } ---------------------------------------- EVENTS (scope: read:events, write:events) ---------------------------------------- Events are company calendar events with date, time, location, and optional group targeting. They are automatically published on creation and added to the calendar. GET /events — List published events (paginated) GET /events/{id} — Get a single event POST /events — Create and publish an event PUT /events/{id} — Update an event DELETE /events/{id} — Delete an event (returns 204) Create request body: { "title": "Team Building Day", "body": "Join us for a fun day of activities.", "starts_at": "2026-05-01", "starts_time": "09:00", "ends_at": "2026-05-01", "ends_time": "17:00", "location": "Main Office", "groups": [1, 3] } Update request body (all fields optional): { "title": "Updated title", "body": "Updated body", "location": "New Venue" } Response: { "data": { "id": 789, "title": "Team Building Day", "body": "Join us for a fun day of activities.", "starts_at": "2026-05-01", "starts_time": "09:00", "ends_at": "2026-05-01", "ends_time": "17:00", "location": "Main Office", "author": { "id": 1, "name": "John Doe", "email": "john@example.com" }, "groups": [ { "id": 1, "name": "All Staff", "type": "custom", "created_at": "2026-01-15T10:00:00+00:00" } ], "is_published": true, "comments_count": 0, "completions_count": 0, "published_at": "2026-05-01T10:00:00+00:00", "created_at": "2026-05-01T10:00:00+00:00", "updated_at": "2026-05-01T10:00:00+00:00" } } ---------------------------------------- CALENDAR ENTRIES (scope: read:calendar_entries, write:calendar_entries) ---------------------------------------- Simple date/time entries that appear in the employee calendar. Automatically published and added to calendar on creation. Body is optional. Entries can be global (no groups) or targeted. GET /calendar-entries — List calendar entries (paginated) GET /calendar-entries/{id} — Get a single calendar entry POST /calendar-entries — Create a calendar entry PUT /calendar-entries/{id} — Update a calendar entry DELETE /calendar-entries/{id} — Delete a calendar entry (returns 204) Create request body: { "title": "Company Holiday", "starts_at": "2026-06-01", "starts_time": "09:00", "ends_at": "2026-06-01", "ends_time": "17:00", "body": "Optional description", "groups": [1, 3] } Response: { "data": { "id": 101, "title": "Company Holiday", "body": null, "starts_at": "2026-06-01", "starts_time": "09:00", "ends_at": "2026-06-01", "ends_time": "17:00", "author": { "id": 1, "name": "John Doe", "email": "john@example.com" }, "groups": [], "created_at": "2026-06-01T10:00:00+00:00", "updated_at": "2026-06-01T10:00:00+00:00" } } ---------------------------------------- USERS (scope: read:users) ---------------------------------------- Read-only. Returns only registered users (those who accepted an invite). GET /users — List users (paginated). Filter: ?group_id={id} GET /users/{id} — Get a single user Response: { "data": { "id": 42, "name": "John Doe", "email": "john@example.com", "type": "user", "groups": [ { "id": 1, "name": "Kitchen Staff", "type": "department", "created_at": "2026-01-15T10:00:00+00:00" } ], "hired_at": "2025-06-01", "is_away": false, "last_active_on": "2026-03-22", "registered_at": "2025-06-01T09:00:00+00:00", "created_at": "2025-06-01T09:00:00+00:00" } } ---------------------------------------- GROUPS (scope: read:groups) ---------------------------------------- Read-only. Returns all groups except system groups. GET /groups — List groups (paginated). Filter: ?type=department|profession|custom GET /groups/{id} — Get a single group Response: { "data": { "id": 1, "name": "Kitchen Staff", "type": "department", "created_at": "2026-01-15T10:00:00+00:00" } } ---------------------------------------- STATISTICS (scope: read:stats) ---------------------------------------- Platform analytics and usage metrics. All responses are cached server-side. GET /stats/users — User counts and activity summary (cached 1h) GET /stats/engagement — Engagement time series (cached 24h) Query params: granularity (day|week|month, default: week), scope (number of periods, default: 12), group_ids (comma-separated) GET /stats/adoption — Adoption rate by department (cached 1h) Query params: group_ids (comma-separated, optional) GET /stats/content — Content activity this week vs last week (cached 1h) Response (/stats/users): { "data": { "total": 1250, "registered": 1100, "active_yesterday": 340, "active_7d": 780, "active_30d": 1050, "new_yesterday": 5, "new_7d": 32, "new_30d": 95 } } Response (/stats/engagement?granularity=week&scope=4): { "data": [ { "year": 2026, "week": 10, "total_users": 1250, "app_users": 1100, "active_users": 340, "app_time": 52800, "sessions": 890 } ] } Response (/stats/adoption): { "data": { "adoption_rate": { "average": 72.5, "departments": [ { "id": 1, "name": "Kitchen", "active": 18, "not_active": 7, "total_employees": 25, "average": 72.0 } ] } } } Response (/stats/content): { "data": { "posts": { "this_week": 45, "last_week": 38, "total": 1250 }, "comments": { "this_week": 120, "last_week": 95, "total": 4800 }, "reactions": { "this_week": 230, "last_week": 210, "total": 8900 }, "chat_messages": { "this_week": 560, "last_week": 480, "total": 22000 }, "todo_tasks_completed": { "this_week": 85, "last_week": 72, "total": 3400 } } } ======================================== PAGINATION ======================================== All list endpoints support ?page= and ?per_page= (max 50, default 25). All list endpoints support ?since= (ISO 8601 date/datetime) to filter to records created after a given timestamp. Useful for incremental sync. Response format for list endpoints: { "data": [ ... ], "links": { "first": "https://customer.monotree.com/api/open/v1/walls/1/posts?page=1", "last": "https://customer.monotree.com/api/open/v1/walls/1/posts?page=4", "prev": null, "next": "https://customer.monotree.com/api/open/v1/walls/1/posts?page=2" }, "meta": { "current_page": 1, "last_page": 4, "per_page": 25, "total": 92, "from": 1, "to": 25 } } To fetch all pages, follow links.next until it is null. ======================================== INCOMING WEBHOOKS ======================================== Incoming webhooks let external systems send messages into Monotree chat rooms or create posts on walls. No bearer token needed — the webhook token is embedded in the URL. POST https://customer.monotree.com/api/open/v1/incoming/{token} The payload is the same for all actions — just a "text" field (Slack-compatible): { "text": "Your message here" } Actions: chat_message (send to chat room), wall_post (create post on wall), calendar_entry (add to calendar). For chat_message and wall_post, the payload is a simple "text" field (Slack-compatible). For calendar_entry, the payload is: { "title": "...", "starts_at": "YYYY-MM-DD", "starts_time": "HH:MM", "ends_at": "YYYY-MM-DD", "ends_time": "HH:MM" } (only title and starts_at are required). Attaching media on chat_message and wall_post: Incoming webhooks can include images, videos, or documents by passing a "media" array of URL + type pairs. Because webhooks have no bearer token, the two-step upload + media_ids flow used by the regular API does not apply — instead, Monotree fetches each URL and creates the Media records server-side. Mirrors the Slack / Discord webhook pattern. { "text": "Image post from webhook", "media": [ { "url": "https://example.com/photo.jpg", "type": "image" }, { "url": "https://example.com/report.pdf", "type": "document" } ] } Rules: - All items must be the same type (image / video / document). Mixed → 422. - Maximum 10 items per payload. - URLs must be publicly reachable from Monotree. Failed downloads → 422 with a descriptive error. - Per-URL download timeout: 15 seconds. Example: curl -X POST https://customer.monotree.com/api/open/v1/incoming/YOUR_TOKEN \ -H "Content-Type: application/json" \ -d '{"text": "Alert: Kitchen cooler temperature is 12°C"}' Example with image: curl -X POST https://customer.monotree.com/api/open/v1/incoming/YOUR_TOKEN \ -H "Content-Type: application/json" \ -d '{"text": "See photo", "media": [{"url": "https://example.com/cooler.jpg", "type": "image"}]}' ======================================== OUTGOING WEBHOOKS ======================================== Outgoing webhooks send event notifications to your URL when things happen in Monotree. Available events: - monotree.post.created — A post was created - monotree.comment.created — A comment was added to a post - monotree.comment.updated — A comment on a post was edited - monotree.comment.deleted — A comment on a post was deleted - monotree.announcement.published — An announcement was published - monotree.formresponse.created — A form response was submitted - monotree.formresponse.updated — A form response was updated. Fires when the response's status, deadline_at, is_hidden/archived flag, or assignees list changes. Does NOT fire on initial creation (use formresponse.created) or when unrelated entities like comments/notes are added. Filterable by form. Whistleblower forms never dispatch. - monotree.user.registered — A user registered (accepted invite) - monotree.onboarding.completed — A user completed onboarding Payload format: { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "monotree.post.created", "version": "v1", "timestamp": 1711270800, "payload": { "id": 123, "body": "Post content here", "wall_id": 1, "wall_name": "General", "author": { "id": 1, "name": "John Doe", "email": "john@example.com" }, "created_at": "2026-03-22T10:00:00+00:00", "updated_at": "2026-03-22T10:00:00+00:00" } } Signature verification: Every delivery includes two headers: Signature and Timestamp. The signature is HMAC-SHA256(timestamp + "." + raw_json_body, your_webhook_secret) as a hex string. Reject requests where the timestamp is more than 5 minutes old to prevent replay attacks. ======================================== OUTGOING REPORTS ======================================== Outgoing reports deliver aggregated data to your URL on a schedule. They use the same signed delivery infrastructure as outgoing webhooks (HMAC-SHA256 signature, Timestamp header, authentication, retries, and delivery logs). Reports are configured in the Outgoing Reports tab. Each report has a type and a schedule. Available report types: - todo_list_stats — Aggregated daily todo list completion statistics by department Schedules: - daily — Delivered every day at 07:00 CET - weekly — Delivered every Monday at 07:00 CET Payload format (todo_list_stats): { "id": "550e8400-e29b-41d4-a716-446655440000", "event": "monotree.todoliststats.daily", "version": "v1", "timestamp": 1711270800, "payload": { "data": { "total_lists": 42, "total_tasks": 150, "completed_tasks": 95, "departments": [ { "id": 1, "name": "Kitchen", "list_count": 5, "total_tasks": 45, "completed_tasks": 32, "completion_percentage": 71.11, "todo_list_templates": { "data": [ { "id": 1, "title": "Morning Checklist", "list_count": 2, "task_count": 15, "completed_count": 10, "task_templates_count": 3, "completion_percentage": 66.67, "todo_tasks": { "data": [ { "id": 101, "title": "Check cooler temperatures", "task_count": 5, "completed_count": 3, "completion_percentage": 60, "tasks": { "data": [ { "id": 1001, "user": { "name": "John Doe" }, "checked_at": "2026-03-25T07:30:00Z" } ] }, "meta": { "total_tasks": 5, "completed_tasks": 3, "completion_percentage": 60 } } ] } } ] } } ] } } } Reports support the same authentication options as outgoing webhooks (Bearer token, Basic Auth, API Key, custom headers). Signature verification works identically — see the Outgoing Webhooks section. Use the Test button in the Outgoing Reports tab to send a test report with yesterday's data. ======================================== RATE LIMITING ======================================== 60 requests per minute per token (configurable per server). Response headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset On 429 responses: Retry-After header with seconds to wait. Incoming webhooks have the same rate limit per webhook. ======================================== ERROR RESPONSES ======================================== 401 Unauthorized: { "error": "unauthorized", "message": "Invalid or inactive API key." } 403 Forbidden: { "error": "forbidden", "message": "This token does not have the required scope: write:posts" } 404 Not Found: Resource not found (no body) 422 Validation Error: { "error": "validation_error", "message": "The given data was invalid.", "errors": { "body": ["The body field is required."] } } 429 Rate Limit: { "message": "Too Many Attempts." } ======================================== HTTP STATUS CODES ======================================== 200 — Request succeeded 201 — Resource created successfully 204 — Resource deleted (no content) 401 — Missing or invalid API token 403 — Token lacks required scope 404 — Resource not found 422 — Validation error 429 — Rate limit exceeded ======================================== CHANGELOG ======================================== v0.11.0 — April 2026 - Added Media upload endpoints (write:media scope): POST /media and GET /media/{id} - Extended POST /walls/{wall_id}/posts with optional media_ids array — upload first, then attach by id - Incoming webhook wall_post and chat_message actions now accept an optional media array of URL + type pairs (server fetches URLs, Slack/Discord-style) - Supported media types: image, video, document. Max 10 per post/message, same type across the batch v0.10.0 — April 2026 - Added monotree.formresponse.updated webhook event (filterable by form) - Fires when a form response's status, deadline, visibility/archive flag, or assignees change - Does not fire on initial creation (use formresponse.created) or on unrelated activity like comments/notes - Whistleblower forms are excluded, matching the existing formresponse.created behavior v0.9.0 — April 2026 - Added Comments CRUD endpoints on posts (read:comments, write:comments scopes) - New endpoints: GET/POST /posts/{post_id}/comments and GET/PUT/DELETE /posts/{post_id}/comments/{id} - Added monotree.comment.created, monotree.comment.updated and monotree.comment.deleted webhook events (filterable by wall) - Comment updates are restricted to the author within 30 minutes of creation v0.8.0 — April 2026 - Outgoing webhooks now support resource filtering — optionally select specific forms or walls per event type - Form response webhooks can be scoped to specific forms (multi-select) - Post created webhooks can be scoped to specific walls (multi-select) - Default remains "all" — no filter means you receive all events of that type - Added filters field to webhook create/update API v0.7.0 — April 2026 - Added Calendar Entries CRUD endpoints (read:calendar_entries, write:calendar_entries scopes) - Create, update, and delete calendar entries with optional body and group targeting - Added Create calendar entry action for incoming webhooks v0.6.0 — April 2026 - Added Events CRUD endpoints (read:events, write:events scopes) - Create, update, and delete company events with date, time, location, and group targeting - Events are automatically published and added to the calendar on creation v0.5.0 — March 2026 - Added Statistics read-only endpoints (read:stats scope) - User summary, engagement time series, adoption rate, content activity - All statistics responses are cached server-side (1h–24h) v0.4.0 — March 2026 - Added Walls read-only endpoints with group associations (read:walls scope) - Post endpoints now nested under walls: /walls/{wall_id}/posts - Added Timestamp header to outgoing webhooks for replay attack prevention - Webhook signatures now include the timestamp: HMAC-SHA256(timestamp + "." + payload, secret) - Added bot user avatars for incoming webhooks (defaults to app icon) - Outgoing webhooks now use revoke instead of delete - Incoming webhooks now use unified text field for all actions (Slack-compatible) - Added Pagination section to documentation with meta and links format - Added incoming webhook rate limiting documentation v0.3.0 — March 2026 - Added Users and Groups read-only endpoints (read:users, read:groups scopes) - Added monotree.user.registered, monotree.onboarding.completed, monotree.formresponse.created webhook events - Added IP whitelist per token, configurable rate limit, usage counters - Added content author selection (bot user or yourself) on incoming webhooks - Added Test Webhook buttons for outgoing and incoming webhooks - Added signature verification documentation with Node.js example v0.2.0 — March 2026 - Added Announcements CRUD endpoints (read:announcements, write:announcements scopes) - Added Create post on wall action for incoming webhooks - Added monotree.announcement.published webhook event v0.1.0 — March 2026 - Initial beta release of the Open API - Bearer token authentication with scoped permissions - Posts CRUD endpoints (read:posts, write:posts scopes) - Outgoing webhooks with HMAC-SHA256 signing for post events - Incoming webhooks for sending messages to chat rooms - API request logging and webhook delivery logging - Rate limiting at 60 requests per minute per token - CMS admin interface for managing tokens, webhooks, and logs