REST conventions
The REST API is at https://dash.justmonitorit.com/api/v1. Every operation
an API token can call is in the REST reference;
the machine-readable description is the OpenAPI 3.1 document at
/api/v1/openapi.json.
In it, operations a token can call list the api_token security scheme with
the lowest token role they need, for example
"security": [{"session": []}, {"api_token": ["member"]}].
This page covers what is common to every operation.
Requests
- Authenticate with
Authorization: Bearer jmi_...(Authentication). - Request bodies are JSON with
Content-Type: application/json(anything else is415 unsupported_media_type), at most 64 KiB (413 payload_too_large) and nested at most 32 levels deep. - Field names are
snake_case. Unknown fields are rejected (422 validation, field error codeunknown_field), so a typo never passes silently. - Paths with an organisation start with
/orgs/{org};GET /tokenreturns your token's organisation id.
Responses
- JSON,
snake_case. Every field is present; a missing value isnull, never an omitted key. - Ids are prefixed strings:
org_,mon_(monitor),inc_(incident),con_(contact),ntf_(notification),trc_(path trace),usr_(user),inv_(invitation),tok_(API token), followed by 26 characters. They sort by creation time. An id with the wrong prefix in a path is404 not_found. - Timestamps are RFC 3339 in UTC with milliseconds:
2026-10-06T12:00:00.000Z. - Durations are integers with the unit in the name:
interval_s,timeout_ms,duration_s. - Enums are lowercase
snake_casestrings. New values may be added; treat an unknown value as "other". - Every response has an
X-Request-Idheader andCache-Control: no-store.
Locations are named by their code (lon1, fra1, nyc1) wherever a
request takes a location. GET /locations lists them and their regions
(eu, na, sa, as, oc, af); it needs no token.
Errors
Errors are RFC 7807 problem
documents with Content-Type: application/problem+json:
{
"type": "https://justmonitorit.com/problems/validation",
"title": "Validation failed",
"status": 422,
"code": "validation",
"detail": "2 fields are invalid.",
"request_id": "0192a3c4-7b1e-7c3a-9d2f-5e8a1b2c3d4e",
"errors": [
{ "pointer": "/spec/url", "code": "target_not_public", "message": "Address resolves to a private network." },
{ "pointer": "/interval_s", "code": "below_plan_minimum", "message": "Your plan allows intervals of 60 s or more." }
]
}
Switch on code, not on title or detail (those are for people and may
change). errors is present only for 422 validation; each pointer is a
JSON Pointer into your request body, or /query/<name> for a query
parameter. All invalid fields are reported at once.
code | Status | Meaning |
|---|---|---|
bad_request | 400 | Malformed JSON, JSON nested too deeply, bad query string |
unauthenticated | 401 | No token, an unknown, expired or revoked token, or an endpoint that needs a signed-in person |
forbidden | 403 | The token's effective role is too low, or the action is owner-only or needs a signed-in person |
account_suspended | 403 | The token's creator is suspended |
org_suspended | 403 | The organisation is suspended |
org_deleting | 403 | The organisation is scheduled for deletion |
billing_inactive | 403 | The subscription has ended; reads still work, writes do not |
plan_limit | 403 | The change would exceed the plan; limit, current and max say which and by how much |
not_found | 404 | No such resource in this organisation (also another organisation's id) |
conflict | 409 | The resource is in a state that does not allow this; detail says why |
contact_unverified | 409 | A test send to a contact that is not verified yet |
contact_disabled | 409 | A test send to a disabled contact |
incident_resolved | 409 | Acknowledging an incident that is already resolved |
sms_allowance_exhausted | 409 | The organisation's SMS credits for this month are used up |
idempotency_in_progress | 409 | A request with the same Idempotency-Key is still running |
precondition_failed | 412 | If-Match is stale: someone changed the resource |
payload_too_large | 413 | Body over 64 KiB |
unsupported_media_type | 415 | Body is not application/json |
validation | 422 | Invalid fields, listed in errors |
idempotency_mismatch | 422 | An Idempotency-Key reused with a different body |
rate_limited | 429 | Too many requests; wait Retry-After seconds |
internal | 500 | Our bug. It is logged with the request id |
not_leader | 503 | A server failover is in progress; retry after Retry-After (2) seconds |
unavailable | 503 | Overloaded or a dependency is down (for example no probe can run a preview check); retry later |
Common field error codes in errors[].code: required, too_short,
too_long, out_of_range, invalid_format, invalid_id,
unknown_field, invalid_choice, not_allowed, duplicate,
target_not_public (the target resolves only to private or reserved
addresses), host_rate_exceeded (your monitors would check one host more
than 120 times a minute), too_few_locations, too_many_locations,
below_plan_minimum, unsupported_tld (a domain without an RDAP server),
phone_country_not_allowed, phone_not_mobile, phone_not_callable (a
premium-rate, toll-free, shared-cost, personal or VoIP number given for a
phone call contact), discord_webhook_invalid,
pushover_invalid, pagerduty_key_invalid, slack_webhook_invalid,
teams_webhook_invalid, teams_webhook_retired (an Office 365 connector
URL, which Microsoft retired in May 2026: use a Workflows webhook),
webhook_url_invalid, webhook_domain_unverified.
Retrying. Retry 429 after Retry-After, and 503 after
Retry-After (or a few seconds) with backoff. Do not retry other 4xx
responses unchanged. A POST that creates something is safe to retry only
with an Idempotency-Key (below).
Pagination
Lists that can grow are paginated with cursors:
GET /orgs/{org}/incidents?limit=100&cursor=djE6WyIyMDI2...
{ "data": [ ... ], "next_cursor": "djE6WyIyMDI2..." }
Pass next_cursor as cursor to get the next page; it is null on the last
page. limit defaults to 50 and is at most 200, except monitors (500) and
check results (1000). Cursors are opaque: do not build or change them. Keep
the other query parameters the same between pages. Lists stay stable while
new items are added.
Short lists (members, a monitor's contacts, status timelines) return
{"data": [...]} without a cursor.
Idempotency keys
POST requests that create something accept an Idempotency-Key header:
creating a monitor, a contact or an invitation, and sending a test
notification. The key is 8-64 characters of A-Z a-z 0-9 _ -; use a new
random value (a UUID) for each intended creation.
- Retrying with the same key and the same body returns the stored response
(with
Idempotent-Replayed: true) instead of creating a duplicate. - The same key with a different body is
422 idempotency_mismatch. - While the first request is still running, a retry gets
409 idempotency_in_progress.
Keys are remembered for 24 hours. They belong to you, so your tokens and dashboard sessions share the key space.
Concurrency: ETag and If-Match
Monitors and contacts have a version that increases with every change.
Their responses carry ETag: "<version>". Send it back on PATCH, PUT
or DELETE:
PATCH /orgs/{org}/monitors/{mon}
If-Match: "7"
Content-Type: application/json
{"interval_s": 300}
If someone changed the monitor since version 7, the answer is
412 precondition_failed and nothing is written: fetch it again and decide.
Without If-Match the change is applied to the latest version.
Updates (PATCH)
PATCH uses JSON Merge Patch (RFC 7396)
with Content-Type: application/json:
- a field you omit is unchanged;
nullclears a field that may be null;- arrays are replaced whole (send the complete list of
labelsorcontact_ids); - a monitor's
specis merged field by field, so{"spec": {"timeout_ms": 5000}}changes only the timeout.
A monitor's or contact's kind cannot change (422, code not_allowed).
Rate limits
| What | Limit |
|---|---|
Reads (GET) | 600 a minute per token |
| Writes | 120 a minute per user (all their tokens and sessions) and 120 a minute per organisation |
| Rejected tokens | 30 a minute per IP address; beyond that, requests with a bad token are 429 |
Preview checks (POST .../monitors/preview-check) | 10 a minute per organisation |
Path traces (POST .../monitors/{mon}/traces) | 10 a minute per organisation |
Invitations (POST .../invites) | 20 a day per organisation |
Verification codes (POST .../contacts/{con}/verify/send) | 3 an hour per contact and 10 a day per organisation; SMS and phone calls together 3 a day per phone number, email 5 a day per address |
| Verifying a code | 5 attempts per code, then the code is void |
Test notifications (POST .../contacts/{con}/test) | 5 per 10 minutes per contact and 30 an hour per organisation |
Over a limit the answer is 429 rate_limited with Retry-After in seconds.
GraphQL queries and MCP tool calls count as the REST requests they make.
Plan limits
What an organisation may do depends on its plan: the number of monitors and
contacts, the shortest interval, locations per monitor, checks per minute,
seats, SMS credits and how long results are kept. GET /orgs/{org}/entitlements returns the limits and the current usage. A change
that would exceed one is 403 plan_limit with the limit that is hit, the
current usage and the max. Results and history older than the plan's
retention are not returned.
Versioning
The major version is in the path (/api/v1). We add endpoints, response
fields and enum values without a new version, so ignore fields you do not
know and handle unknown enum values. A breaking change would ship as
/api/v2 beside v1. Every response carries X-JMI-Build, the
server build.