Skip to main content

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:

EndpointWhereParametersDefault page size
POST /conversations/getrequest bodypage, limit20
POST /logs/getrequest bodypage, limit50
GET /notifications/getquery stringlimit, skip, unreadOnly50

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/json for 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.