REST conventions | JustMonitorIt API
On this page

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 is 415 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 code unknown_field), so a typo never passes silently.
  • Paths with an organisation start with /orgs/{org}; GET /token returns your token's organisation id.

Responses

  • JSON, snake_case. Every field is present; a missing value is null, 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 is 404 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_case strings. New values may be added; treat an unknown value as "other".
  • Every response has an X-Request-Id header and Cache-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.

codeStatusMeaning
bad_request400Malformed JSON, JSON nested too deeply, bad query string
unauthenticated401No token, an unknown, expired or revoked token, or an endpoint that needs a signed-in person
forbidden403The token's effective role is too low, or the action is owner-only or needs a signed-in person
account_suspended403The token's creator is suspended
org_suspended403The organisation is suspended
org_deleting403The organisation is scheduled for deletion
billing_inactive403The subscription has ended; reads still work, writes do not
plan_limit403The change would exceed the plan; limit, current and max say which and by how much
not_found404No such resource in this organisation (also another organisation's id)
conflict409The resource is in a state that does not allow this; detail says why
contact_unverified409A test send to a contact that is not verified yet
contact_disabled409A test send to a disabled contact
incident_resolved409Acknowledging an incident that is already resolved
sms_allowance_exhausted409The organisation's SMS credits for this month are used up
idempotency_in_progress409A request with the same Idempotency-Key is still running
precondition_failed412If-Match is stale: someone changed the resource
payload_too_large413Body over 64 KiB
unsupported_media_type415Body is not application/json
validation422Invalid fields, listed in errors
idempotency_mismatch422An Idempotency-Key reused with a different body
rate_limited429Too many requests; wait Retry-After seconds
internal500Our bug. It is logged with the request id
not_leader503A server failover is in progress; retry after Retry-After (2) seconds
unavailable503Overloaded 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;
  • null clears a field that may be null;
  • arrays are replaced whole (send the complete list of labels or contact_ids);
  • a monitor's spec is 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

WhatLimit
Reads (GET)600 a minute per token
Writes120 a minute per user (all their tokens and sessions) and 120 a minute per organisation
Rejected tokens30 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 code5 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.