Errors and conventions
This page describes how the API actually behaves. Some of it is not what a REST client expects, and a client written on assumptions will misread responses.
Check the body, not the status
Errors come back in this shape:
{ "error": true, "message": "Malformed request. name is required." }
Some of them arrive with HTTP 200. Validation failures in particular are
frequently returned as 200 OK with error: true in the body. Authentication
and authorization failures do use 401 and 403 correctly, but you cannot rely on
the status code alone anywhere else.
The only safe rule:
const response = await fetch(url, { headers });
const body = await response.json();
if (!response.ok || body.error === true) {
throw new Error(body.message || `Request failed with ${response.status}`);
}
An AI agent should apply the same rule before interpreting any result. A response that reads as success at the HTTP layer can be a refusal at the application layer.
Success responses have no fixed envelope
There is no data wrapper. Each endpoint returns its payload under its own key:
{ "properties": [ ... ] }
{ "property": { ... } }
{ "booking": { ... } }
The API reference names the endpoint; the key follows the resource. Read one response from each endpoint you use and shape your client around it.
Pagination is the exception, not the rule
Most list endpoints return everything the key can see, in one response.
GET /properties/get/all on a large portfolio returns every property. There is
no page, limit, offset, or cursor parameter, and no total count.
Three endpoints do paginate, each in its own way:
| Endpoint | Where | Parameters | Default page size |
|---|---|---|---|
POST /conversations/get | request body | page, limit | 20 |
POST /logs/get | request body | page, limit | 50 |
GET /notifications/get | query string | limit, skip, unreadOnly | 50 |
POST /conversations/get returns total, page, and limit alongside the
results, so you can tell when there is more. The other two do not, so read until
a page comes back shorter than the limit you asked for.
Everywhere else, assume no pagination:
- Set a generous client timeout on list calls.
- Do not poll list endpoints in a tight loop; cache what you read.
- If you need a subset, filter client-side — the API will not do it for you.
No outbound webhooks
MTRO PRO does not call your server. The /webhooks/ endpoints are inbound —
either provider callbacks the platform receives, or the lead CRUD described in
the data model. There is no way to subscribe to
"booking created" or "message received".
To notice a change, poll. A sensible cadence is every few minutes for conversations and leads, and less often for properties and vendors, which change rarely. Compare against what you read last time and act on the difference.
Dates and ids
- Dates and times are millisecond timestamps, not seconds, not ISO strings.
- Ids are MongoDB ObjectIds: 24 hexadecimal characters.
- Booking dates are interpreted in the property's own timezone, which is on the property record.
Request format
Content-Type: application/jsonfor every request with a body.- Most write operations are
POST, including updates and deletes. Do not assume a RESTful method; the reference states the method for each endpoint. - Request bodies are capped at 50 MB.
Rate limits
Authentication, password recovery, and public endpoints are rate limited, and exceeding a limit returns HTTP 429. The authenticated integration endpoints in this documentation do not currently enforce a documented per-key limit, but that is not a guarantee — build in retry with backoff on 429 and 5xx, and do not hammer list endpoints.
Writes are real
There is no sandbox flag on production. Sending a message sends it. Creating a
payment creates it. Use https://dev.core.mtro.app with a development key to
test anything that writes.