HTTP API
Monitors, pages, sharing, teams, alert channels and pixels over HTTP, with keys you can limit by permission and page.
Everything you do in the dashboard, apart from your password, sign-in methods, billing and API keys, you can also do with the HTTP API. Create a key under API keys and send it as a bearer token. All endpoints are under https://vivere.dev/api/v1, take and return JSON, and see what your account sees in the dashboard, within what the key is allowed.
curl -H "Authorization: Bearer vv_…" https://vivere.dev/api/v1/monitors
The same key works for the MCP server, whose tools call these endpoints.
Key permissions
A key has full access, which covers everything below and anything the API learns to do later, or the permissions you choose for it, one per resource:
| Permission | Read allows | Write also allows |
|---|---|---|
monitors | Monitors, their runs and run output, their status changes. Over MCP, check_in too, because a monitor's ping URL is enough to check in. | Creating, changing, pausing, resuming and deleting monitors. |
pages | Pages, their public address, and on your own pages who they are shared with. | Creating, renaming and deleting pages, making a page private, sharing with people and teams. |
teams | Teams, their members and pages. | Creating, renaming and deleting teams, adding and removing members. |
channels | Alert channels. Secret settings are never returned. | Creating, changing, deleting and testing channels. |
pixels | Tracking pixels and their loads. | Creating and deleting pixels, setting and removing a pixel's image. |
events | Status changes across your own monitors. | (read only) |
A key can also be limited to some pages, your own or ones shared with you. It then sees nothing on any other page, as if those monitors and pages did not exist, and it cannot create pages. Monitors it creates without naming a page go on the first page it has. A key can be given an expiry of 30, 90 or 365 days; after that it answers 401.
A key only ever narrows what its account can do: on a page shared with you to view, a full-access key still only views. Change a key's permissions and pages at any time under API keys; the change applies to its next request. GET /account says what a key may do, whatever it was granted.
Endpoints
Account
| Method and path | Needs | What it does |
|---|---|---|
GET /account | any key | Your account and plan, the plan's limits and what you use of them, and this key's permissions, all_pages, pages and expires_at. |
Monitors
| Method and path | Needs | What it does |
|---|---|---|
GET /monitors | monitors:read | Monitors on every page the key can see, each with its page and access. ?page=<id> for one page. |
POST /monitors | monitors:write | Create a monitor on your first page, or on "page": <id>. Returns it, including ping_url for heartbeats. |
GET /monitors/{id} | monitors:read | One monitor, with the channels it alerts. |
PATCH /monitors/{id} | monitors:write | Change a monitor. Only the fields sent change; the kind cannot. |
DELETE /monitors/{id} | monitors:write | Delete it and its history. |
POST /monitors/{id}/pause, /resume | monitors:write | Pause or resume. |
GET /monitors/{id}/runs | monitors:read | A heartbeat monitor's newest runs (?limit=, default 50, up to 200) with outcome, times, exit code and any model, token counts, cost_usd and items they reported, plus cost_30d_usd and the monitor's cost_alert_usd. |
GET /runs/{id} | monitors:read | One run, with the output it sent as log, its address and user agent. |
GET /monitors/{id}/events | monitors:read | The monitor's status changes, newest first (?limit=). |
GET /pages/{page}/monitors/{slug} | monitors:read | One monitor by page and slug, at any access, viewer included. |
Pages and sharing
| Method and path | Needs | What it does |
|---|---|---|
GET /pages | pages:read | Your pages, then the pages shared with you, each with access: owner, editor or viewer. |
POST /pages | pages:write | Create a page: {"title": "…", "slug": "…"}. The slug is made from the title when left out. |
GET /pages/{page} | pages:read | One page and its public url; on your own pages, its shares and team_shares. |
PATCH /pages/{page} | pages:write | Change its title or slug. |
DELETE /pages/{page} | pages:write | Delete an empty page that is not your first. |
POST /pages/{page}/private | pages:write | Turn off Public on every monitor on it, which takes its status page down. |
POST /pages/{page}/shares | pages:write | Share with the account of a login: {"email": "…", "role": "viewer"} or "editor". Sending it again changes the role. |
DELETE /pages/{page}/shares/{account} | pages:write | Stop sharing with that account. |
POST /pages/{page}/teams | pages:write | Share with one of your teams: {"team": <id>, "role": "editor"}. |
DELETE /pages/{page}/teams/{team} | pages:write | Stop sharing with that team. |
Only a page's owner changes or shares it; on a page shared with you these answer 403. Sharing follows the same rules as the dashboard: it needs a paid plan, everyone you share with takes one of your plan's seats, and a share past your seats is listed with "active": false and gives nothing until a seat frees up.
Teams
| Method and path | Needs | What it does |
|---|---|---|
GET /teams | teams:read | Your teams, with member_count and page_count. |
POST /teams | teams:write | Create a team: {"name": "…"}. Needs the Team plan. |
GET /teams/{id} | teams:read | Its members and pages. |
PATCH /teams/{id} | teams:write | Rename it: {"name": "…"}. |
DELETE /teams/{id} | teams:write | Delete it. |
POST /teams/{id}/members | teams:write | Add the account of a login: {"email": "…"}. |
DELETE /teams/{id}/members/{account} | teams:write | Remove a member. |
Alert channels
| Method and path | Needs | What it does |
|---|---|---|
GET /channels | channels:read | Your channels. |
POST /channels | channels:write | Create one: {"kind": "webhook", "name": "…", "config": {…}}. |
GET /channels/{id} | channels:read | One channel. |
PATCH /channels/{id} | channels:write | Change its name or config. Settings left out keep their value. |
DELETE /channels/{id} | channels:write | Delete it. |
POST /channels/{id}/test | channels:write | Send a test alert (five a minute). |
Settings by kind: email to; webhook url, secret; slack url; discord url; telegram token, chat_id; ntfy url, token. The webhook secret, the Slack and Discord webhook URLs and the Telegram and ntfy tokens are secret: they are never returned, and secrets_set names the ones that have a value. Slack, Discord, Telegram and ntfy need a paid plan. See Alert channels for what each sends.
Pixels
| Method and path | Needs | What it does |
|---|---|---|
GET /pixels | pixels:read | Your pixels, each with its url, the tag to paste, and its hit count. |
POST /pixels | pixels:write | Create one: {"name": "…"}. |
GET /pixels/{id} | pixels:read | One pixel and its newest loads (?limit=, default 50, up to 200). |
DELETE /pixels/{id} | pixels:write | Delete it and its loads. |
PUT /pixels/{id}/image | pixels:write | Serve an image instead of the invisible GIF: send the PNG, GIF, JPEG or WebP file as the body. On plans with pixel images. |
DELETE /pixels/{id}/image | pixels:write | Go back to the invisible GIF. |
Events
| Method and path | Needs | What it does |
|---|---|---|
GET /events | events:read | Status changes across your own monitors, newest first (?limit=, default 50, up to 200). |
Monitor fields
POST /monitors and PATCH /monitors/{id} take the same fields. All are optional on a change; a new monitor needs a name.
| Field | Kind | Meaning |
|---|---|---|
name, tags, description | both | Tags are space-separated. |
kind | both | heartbeat (default) or http. Only on create. |
page | both | Page id. On a change, only the page's owner can move a monitor, and only to another of their own pages. |
period, grace | heartbeat | Durations like 30m, 1h, 1d. Setting period on a cron monitor switches it back to a period. |
cron, timezone | heartbeat | A five-field cron expression and an IANA timezone, instead of period. An empty cron switches back to the period. |
cost_alert_usd | heartbeat | Alert when one run reports a cost above this. 0 turns it off. |
url, method, interval | http | What to request (GET, HEAD or POST) and how often. |
expected_status, keyword | http | The status to expect (0 accepts any 2xx or 3xx) and text the body must contain. |
timeout, fail_threshold | http | Seconds (1 to 60), and failed checks in a row before it is down (1 to 10). |
channels | both | Ids of the alert channels to notify. On a change it replaces the channels you can see; other editors' channels stay. |
public | both | Show it on the page's public status page. |
Pages shared with you
On a page shared with you as an editor, monitors work like your own: they have an id and a ping_url, you can change, pause, resume and delete them, and a monitor you create there belongs to the page's owner and counts against their plan. On a page shared with you to view, a monitor comes back with its slug, name, kind, status and last ping or check, and without its id, ping URL, target or schedule, because a heartbeat's id is its ping URL. Address it with GET /pages/{page}/monitors/{slug}; an id route answers 404 for it, as it would for somebody else's monitor.
Examples
Create a heartbeat:
curl -X POST -H "Authorization: Bearer vv_…" -H "Content-Type: application/json" \
-d '{"name":"nightly-db-backup","kind":"heartbeat","period":"1d","grace":"30m","tags":"prod","channels":[3]}' \
https://vivere.dev/api/v1/monitors
Use "cron":"0 2 * * *","timezone":"Europe/Berlin" instead of period for a cron schedule.
Create an HTTP check:
curl -X POST -H "Authorization: Bearer vv_…" -H "Content-Type: application/json" \
-d '{"name":"api health","kind":"http","url":"https://api.example.com/healthz","interval":"5m","expected_status":200,"keyword":"ok"}' \
https://vivere.dev/api/v1/monitors
Give a monitor more grace and a cost alert:
curl -X PATCH -H "Authorization: Bearer vv_…" -H "Content-Type: application/json" \
-d '{"grace":"1h","cost_alert_usd":0.50}' https://vivere.dev/api/v1/monitors/<id>
Make a page for a client and share it with them to view:
curl -X POST -H "Authorization: Bearer vv_…" -H "Content-Type: application/json" \
-d '{"title":"Acme jobs"}' https://vivere.dev/api/v1/pages
curl -X POST -H "Authorization: Bearer vv_…" -H "Content-Type: application/json" \
-d '{"email":"ops@acme.example","role":"viewer"}' https://vivere.dev/api/v1/pages/<page>/shares
Add a Slack channel:
curl -X POST -H "Authorization: Bearer vv_…" -H "Content-Type: application/json" \
-d '{"kind":"slack","name":"#ops","config":{"url":"https://hooks.slack.com/services/…"}}' \
https://vivere.dev/api/v1/channels
Give a pixel an image:
curl -X PUT -H "Authorization: Bearer vv_…" --data-binary @logo.png https://vivere.dev/api/v1/pixels/<id>/image
Errors
Errors are {"error": "message"}, with the message written for a person to read:
| Status | Means |
|---|---|
400 | The body is not JSON, or has a field the endpoint does not know. |
401 | No key, an unknown key, or one that has expired. |
402 | A plan limit: monitors, pages, pixels, seats, or a feature the plan does not have. |
403 | The key lacks the permission the endpoint needs (the message names it), or only the page's owner can do this. |
404 | Not found, or not something this key can see. |
409 | A slug or name already in use, or a page that cannot be deleted yet. |
422 | A value that is not valid, such as a period shorter than your plan allows. |
429 | Rate limited: 60 requests a minute per address. |