Forms
Read-only access to forms and their responses. List forms, page through a form's responses, fetch a single response with its full comment context, or pull everything that changed since a timestamp — built for external dashboards, BI tools, and historical analysis.
Required scopes: read:forms for the form endpoints, read:formresponses for the response endpoints.
Forms are a feature that is switched on per company. While it is off, every endpoint on this page returns 403 with the message The forms feature is not enabled., even with the right scope.
Endpoints
/api/open/v1/formsList forms (published by default, paginated)
/api/open/v1/forms/{id}Get a single form
/api/open/v1/forms/{id}/responsesList a form's responses (light payload, paginated)
/api/open/v1/formresponsesBulk feed of responses incl. form, comments and notes — ?since= for sync
/api/open/v1/formresponses/{id}Get a single response incl. form, comments and notes
List forms
curl "https://customer.monotree.com/api/open/v1/forms?status=published" \
-H "Authorization: Bearer mono_your_token_here"| Param | Notes |
|---|---|
status | published (default), draft, or all. Any other value returns 422. |
since | Only forms created at or after this timestamp — ISO 8601, e.g. 2026-05-20T08:00:00Z (see timestamp format). |
per_page | Default 25, max 50. |
Forms are listed newest first, by created_at. GET /forms/{id} returns the form whether it is published or not; a deleted form returns 404.
Form object
{
"data": {
"id": 12,
"title": "Maintenance Request",
"description": "Report broken equipment",
"instructions": "Fill out all fields",
"respondent_identity": "required",
"is_anonymous": false,
"is_commentable": true,
"allow_multiple_responses": true,
"is_published": true,
"published_at": "2026-05-01T08:00:00+02:00",
"due_at": "2026-06-01",
"groups": [
{ "id": 3, "name": "Kitchen Staff", "type": "department", "created_at": "2026-01-15T10:00:00+01:00" }
],
"responses_count": 118,
"created_at": "2026-04-28T09:00:00+02:00",
"updated_at": "2026-05-01T08:00:00+02:00"
}
}respondent_identity is required, optional, or anonymous; is_anonymous is true when identity is always anonymous. groups is the form's targeting — empty for a company-wide form. due_at is the date (YYYY-MM-DD) by which respondents should have answered — set per form in the CMS and shown in-app to users who have not yet responded; null when the form has no due date. responses_count counts every response that is not deleted, hidden responses included, so it can be higher than the number of responses the response endpoints return.
List a form's responses
A light payload — field responses without the comment threads. Use the single response or the bulk feed for full context.
curl "https://customer.monotree.com/api/open/v1/forms/12/responses?status=done" \
-H "Authorization: Bearer mono_your_token_here"| Param | Notes |
|---|---|
status | Workflow status: todo, in_progress, done, or null (no status). |
since | Only responses created at or after this timestamp — ISO 8601, e.g. 2026-05-20T08:00:00Z (see timestamp format). |
include_deleted | 1/true to also list deleted (archived) responses — their deleted_at is set. |
only_deleted | 1/true to list just the archive. See deleted responses. |
per_page | Default 25, max 100. |
{
"data": {
"id": 4521,
"form_id": 12,
"form_title": "Maintenance Request",
"form_due_at": "2026-06-01",
"deadline_at": "2026-06-05",
"is_anonymous": false,
"status": "done",
"respondent": { "id": 42, "name": "Jane Doe", "email": "jane@example.com" },
"assignees": [ { "id": 5, "name": "Sam Doe", "email": "sam@example.com" } ],
"department": { "id": 3, "name": "Kitchen Staff", "type": "department", "created_at": "2026-01-15T10:00:00+01:00" },
"field_responses": [
{ "value": "Freezer 2 is above temperature", "field": { "id": 7, "title": "What happened?", "type": "text" } },
{ "value": [ { "id": 21, "title": "Urgent" } ], "field": { "id": 8, "title": "Priority", "type": "choices" } }
],
"media": [
{ "id": 55, "type": "image", "mime_type": "image/jpeg", "filename": "freezer.jpg", "size": 182034, "url": "https://…/freezer.jpg", "width": 1280, "height": 960, "tag": "media", "created_at": "2026-05-20T07:45:00+02:00" }
],
"created_at": "2026-05-20T07:45:00+02:00",
"updated_at": "2026-05-20T09:10:00+02:00",
"deleted_at": null
}
}respondent, assignees and comment author are the full Open API user object, shortened in these examples. respondent is null when the response is anonymous. choices fields serialize their selections as { id, title } arrays; users and department_users fields as user objects. Responses are listed newest first, by created_at. Hidden responses are never returned; deleted (archived) responses only when you opt in with include_deleted or only_deleted.
Every response object (light and detail) also carries form_due_at — the parent form's due date (null when the form has none) — deadline_at — the date (YYYY-MM-DD) staff should have handled this response by, set per response in the app or CMS (null when none) — assignees — the staff members currently assigned to handle the response (empty when unassigned) — and a deleted_at that is always present: null while the response exists, an ISO 8601 timestamp once it has been deleted.
Every response object (light and detail) includes media — the files uploaded with the submission. They belong to the response as a whole, not to a specific field. type is image or video, or for a document the detected kind, such as pdf, document or spreadsheet, and url is a direct download link. On anonymous responses the files are stored encrypted, so url is a short-lived signed link (expires after ~5 minutes) — re-fetch the response whenever you need a fresh one.
Get a single response
Includes the embedded form, the shared public_comments thread, and the staff-only internal_comments — the full context for analysis. A deleted (archived) response returns 404 unless you pass ?include_deleted=1.
curl https://customer.monotree.com/api/open/v1/formresponses/4521 \
-H "Authorization: Bearer mono_your_token_here"{
"data": {
"id": 4521,
"form_id": 12,
"...": "…same fields as the list payload…",
"form": { "id": 12, "title": "Maintenance Request", "due_at": "2026-06-01", "...": "…form object, without responses_count…" },
"public_comments": [
{
"id": 901,
"body": "Could you re-check the freezer reading?",
"form_response_id": 4521,
"form_id": 12,
"author": { "id": 5, "name": "Sam Doe", "email": "sam@example.com" },
"media": [
{ "id": 77, "type": "image", "mime_type": "image/jpeg", "filename": "freezer.jpg", "url": "https://cdn.monotree.dk/…/freezer.jpg", "width": 1200, "height": 800 }
],
"created_at": "2026-05-20T08:00:00+02:00",
"updated_at": "2026-05-20T08:00:00+02:00"
}
],
"internal_comments": [
{
"id": 33,
"body": "Escalated to facilities.",
"form_response_id": 4521,
"form_id": 12,
"author": { "id": 5, "name": "Sam Doe", "email": "sam@example.com" },
"created_at": "2026-05-20T08:05:00+02:00",
"updated_at": "2026-05-20T08:05:00+02:00"
}
]
}
}public_commentsis the thread shared with the respondent;authorisnullwhen the comment was written by the respondent on an anonymous response. Attachments arrive inmedia.internal_commentsare staff-only notes — never visible to the respondent in the app, exposed here for complete context.authorisnullwhen the note's author no longer exists, or when the respondent wrote it on an anonymous response.
Responses to a deleted form are not returned by GET /formresponses or GET /formresponses/{id}, and GET /forms/{id}/responses returns 404 for a deleted form.
Bulk feed with since
GET /formresponses returns the same detail payload as the single fetch, paginated across all forms — no need to iterate form ids. Its since parameter filters on updated_at, so one poll captures everything that happened since your last sync: new responses, field or workflow-status edits, and new public or internal comments (each bumps the response's updated_at). Results are ordered by updated_at descending.
curl "https://customer.monotree.com/api/open/v1/formresponses?since=2026-05-20T00:00:00Z" \
-H "Authorization: Bearer mono_your_token_here"| Param | Notes |
|---|---|
since | Responses created or updated at or after this timestamp — ISO 8601, e.g. 2026-05-20T08:00:00Z. Omit for a full dump (initial backfill). |
status | Workflow status: todo, in_progress, done, or null. |
include_deleted | 1/true to also include deleted (archived) responses — their deleted_at is set. Deleting bumps updated_at, so a since poll with this flag catches deletions your sync may have missed. |
only_deleted | 1/true to return just the archive. See deleted responses. |
per_page | Default 25, max 100. |
An invalid since or status returns 422. For push-based integration, the outgoing webhooks fire on response creation, updates, deletions, and comment activity.
Deleted (archived) responses
Responses deleted in the CMS are archived, not destroyed — they keep their id, field responses and comments, and gain a deleted_at timestamp. They are left out of every response endpoint by default; two flags opt in:
| Flag | Works on | Effect |
|---|---|---|
include_deleted=1 | all response endpoints | Deleted responses appear alongside live ones (deleted_at set); the single fetch returns the response instead of 404. |
only_deleted=1 | all response endpoints | Only deleted responses — the archive. Wins when both flags are sent. On the single fetch, a response that is not deleted then returns 404. |
Whistleblower and hidden responses stay excluded regardless of these flags. Restoring a response in the CMS clears deleted_at again, so treat it as state rather than a tombstone.
# everything ever submitted to form 12 that has since been archived
curl "https://customer.monotree.com/api/open/v1/forms/12/responses?only_deleted=1" \
-H "Authorization: Bearer mono_your_token_here"The since timestamp format
Every since parameter on these endpoints takes an ISO 8601 timestamp. Always include the timezone — Z for UTC or an explicit offset:
?since=2026-05-20T08:00:00Z UTC
?since=2026-05-20T10:00:00%2B02:00 UTC+2 (a literal + must be url-encoded as %2B)
?since=2026-05-20 date only — midnight Copenhagen timeValues without a timezone are read as Copenhagen time (Europe/Copenhagen), so send an explicit Z/offset to avoid surprises. Anything that can't be parsed as a date returns 422. When syncing, use the updated_at of the newest record you've already processed (the API returns timestamps in ISO 8601 with the Copenhagen offset; URL-encode its + as %2B), and remember the comparison is inclusive (>=) — records exactly on the boundary appear again.