REST API

Read and write your monitors, read and update incidents, and upload source maps. Everything here uses the same permission checks and the same tenancy filter as the dashboard — there is no separate API-side implementation that could drift from it.

Included on every plan, the free one included. There is no add-on to buy and no tier that unlocks it.

Base URL

https://vitrinaengine.com/api/v1

Authentication

Create a key under Settings → API keys, then send it as a bearer token. The key is shown once, when it is created, and stored only as a hash — if you lose it, make another.

curl https://vitrinaengine.com/api/v1/summary \
  -H "Authorization: Bearer vte_your_key_here"

A key never grants more than the person who created it had. It is bound to their membership, so it carries their role: a key made by somebody with a read-only role cannot write, whatever you send it. A key can also be confined to a single workspace, in which case every response is filtered to that workspace and anything outside it does not exist as far as the key is concerned.

Removing somebody from your organisation revokes the keys they created, in the same operation. Revoking access has to revoke the credentials with it, or the person keeps a working one.

Conventions

Every successful response is a JSON object with a data property. Every failure is a JSON object with an error property holding a sentence meant for a human.

{ "data": { "id": "8f14e45f-…", "name": "Marketing site" } }

{ "error": "This key cannot create monitors." }

Responses are sent Cache-Control: private, no-store. This is per-key data on a shared origin and must never be held by a proxy.

Status codes

CodeMeans
200Fine. 201 when something was created.
400The body was not JSON, a field is missing, or nothing was asked for.
401No key, or not a valid one. Deliberately never says which — a message distinguishing them is a way to test keys.
403A valid key without the permission for this action. The message names it.
404No such record for you. See below.
413A source map over the size cap.
429Over a rate limit. Carries Retry-After. See below.

Rate limits

Per organisation, not per key — minting another key does not raise them.

BucketLimit
Reads (GET)120 a minute
Writes (POST, PATCH, DELETE)30 a minute
Source map uploads300 an hour

Over the limit answers 429 with a Retry-After header giving whole seconds until the window resets. Honour it rather than retrying immediately.

These are set where a normal integration never reaches them: a wallboard polling every ten seconds spends six of the 120 reads. They are the same on every plan, because the collection endpoints return everything in one response — an account with five hundred monitors does not need more requests than one with twenty. Source maps get an hourly bucket because they arrive in bulk at deploy time, where a front end can easily have a hundred chunks.

404 rather than 403, on purpose

An id belonging to another customer answers “Not found.”, not “Forbidden.” A 403 would confirm the record exists, which turns the endpoint into a way to test whether an id is real. Do not read a 404 as proof that nothing exists anywhere — only that nothing exists that this key may see.

Endpoints

GET /summary

Counts, for a wallboard or a daily digest. Needs monitor:read.

{
  "data": {
    "total": 42, "up": 39, "degraded": 1, "down": 1,
    "paused": 1, "pending": 0,
    "openIncidents": 2, "suppressedIncidents": 1
  }
}

suppressedIncidents counts incidents held back because they are blast radius of another one. They are real, and they are not separate outages.

GET /monitors

Every monitor the key can see. Needs monitor:read. Each carries id, name, kind, status, statusSince, enabled, intervalSeconds, tags, workspace, lastCheckedAt, lastResponseTimeMs, lastMessage, uptime24h, uptime30d and openIncidentId.

config is never returned. For some kinds it holds request headers and credentials, and a read-scoped key should not be a way to read back the secrets somebody typed into a form.

POST /monitors

Needs monitor:create. Requires name, kind, workspaceId and config. Optional: intervalSeconds (default 300), confirmations, dependsOn.

curl -X POST https://vitrinaengine.com/api/v1/monitors \
  -H "Authorization: Bearer vte_…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Marketing site",
    "kind": "http",
    "workspaceId": "…",
    "intervalSeconds": 60,
    "config": {
      "url": "https://example.com",
      "assertions": [{ "type": "status_code", "operator": "lt", "value": "400" }]
    }
  }'

Answers 201 with the new id. The interval is clamped to your plan’s floor rather than refused, so asking for 10 seconds on a plan whose floor is 60 gives you 60 — read it back if it matters.

GET /monitors/{id}

One monitor, with everything the list gives plus paused. Needs monitor:read.

PATCH /monitors/{id}

Send only what you want changed: name, intervalSeconds, confirmations, enabled, config, or paused.

paused is checked against monitor:pause and everything else against monitor:update, separately — a key allowed to pause but not to edit can still pause. Sending an empty change is a 400.

The monitor is re-read and returned rather than the input echoed back, so what you see is what was stored.

# Silence a monitor for the length of a deploy.
curl -X PATCH https://vitrinaengine.com/api/v1/monitors/$ID \
  -H "Authorization: Bearer vte_…" \
  -H "Content-Type: application/json" \
  -d '{"paused": true}'

DELETE /monitors/{id}

Needs monitor:delete. Answers { "data": { "id": "…", "deleted": true } }.

GET /incidents

Needs incident:read. Takes ?open=true for currently open ones, and ?limit= (default 50, clamped to 200 — asking for more is answered with 200 rather than refused).

Each carries id, monitorId, monitorName, title, cause, status, severity, startedAt, resolvedAt, durationSeconds, acknowledgedAt and rootIncidentId.

rootIncidentId is the field to look at when building an alert feed. When it is set, this incident is the blast radius of another one — the database host went down and this is one of the twelve services behind it. Skip those and you get one alert instead of thirteen.

GET /incidents/{id}

One incident. Needs incident:read. The same fields as the list, plus workspaceId.

It does not return the incident’s timeline — the comments and status changes shown in the dashboard are not on this endpoint. If you need them, say so and they can be added; documenting them here while they do not exist would be worse than the gap.

PATCH /incidents/{id}

Send status of "acknowledged" or "resolved", or a comment, or both. Each is checked against its own permission: incident:acknowledge, incident:resolve, incident:comment. Adding "publish": true to a comment puts it on the status page and additionally needs status_page:manage.

# A runbook that fixed the thing itself can close its own incident.
curl -X PATCH https://vitrinaengine.com/api/v1/incidents/$ID \
  -H "Authorization: Bearer vte_…" \
  -H "Content-Type: application/json" \
  -d '{"status": "resolved", "comment": "Restarted by runbook.", "publish": true}'

POST /sourcemaps

Upload a source map so minified stack traces resolve. Needs monitor:create. Requires projectRef (a number) and filename, plus the map itself as either map (JSON) or mapGzipBase64. Optional debugId and release.

Send a debug id or a release. A map with neither cannot be matched to a stack trace and will sit there doing nothing. Matching is by debugId first, then release plus filename.

Maps are resolved when an issue is read, not at upload, so one uploaded after the errors arrived still helps — which is the usual order.

What this API deliberately does not return

  • Monitor config. It can contain credentials.
  • Which region a check ran in. Where we check from is ours to decide and ours to change as probes are placed and moved. It would be a promise about our infrastructure that you could not act on, and it is not shown anywhere else in the product either.

Versioning

The version is in the path. Fields will be added to responses — treat unknown ones as something to ignore rather than an error — but nothing will be removed from v1 or change meaning under it.

Anything missing?

The surface is deliberately small: it covers a wallboard, a chat digest and a deploy script that silences a monitor for the length of a release. If you are building something it does not reach, write to support@vitrinaengine.com — knowing what people actually want is how the next endpoint gets chosen.

Pricing · Terms