API Reference
Base URL: https://api.verid.dev
Version: 2026-05-01
All requests must include Authorization: Bearer YOUR_API_KEY where YOUR_API_KEY starts with vrd_. All request and response bodies are JSON.
Pagination is uniform across list endpoints: pass page (default 1, min 1) and limit (default 20, min 1, max 100) as query parameters. List responses always return:
{
"data": [...],
"pagination": {
"page": 1,
"limit": 20,
"total": 42,
"totalPages": 3,
"hasMore": true
}
}Monitors
List monitors
GET /v1/monitors
| Query param | Type | Default | Constraints |
|---|---|---|---|
page | integer | 1 | >= 1 |
limit | integer | 20 | 1–100 |
Create a monitor
POST /v1/monitors
| Field | Type | Required | Default | Constraints / Allowed values |
|---|---|---|---|---|
name | string | yes | — | 1–200 characters |
url | string | yes | — | Must be a valid URL |
schedule_interval_seconds | integer | yes | — | Positive integer; tier-gated minimum (free: 86400, starter: 3600, pro: 900, scale: 300) |
extract_config | object | yes | — | See Extract config |
diff_predicate | object | yes | — | See Diff predicate |
deliveries | array | no | [] | 0–25 items; tier-gated max per monitor (free: 1, starter: 3, pro: 10, scale: 25). See Delivery config |
template_slug | string | no | null | Marks the monitor as created from a template |
fetch_mode | string | no | "auto" | "auto" | "browser" |
request_headers | object | no | null | See Request headers |
Returns 201 Created with the full monitor object.
Extract config
Discriminated by method. One of:
method | Extra fields |
|---|---|
"css" | fields: { [name]: cssSelector } |
"xpath" | fields: { [name]: xpathExpression } |
"json_path" | fields: { [name]: jsonPathExpression } |
"regex" | fields: { [name]: regexPattern } |
"full_page" | (no extra fields — hashes the rendered document) |
"prompt" | prompt: string (10–2000 chars), optional schema: object (JSON-Schema-shaped hints) |
See Extraction methods for worked examples.
Diff predicate
Discriminated by type. One of:
type | Required fields |
|---|---|
"any_field_changes" | — |
"field_changes" | field: string |
"field_increases_by_percent" | field: string, threshold: number (positive) |
"field_decreases_by_percent" | field: string, threshold: number (positive) |
"field_increases_by_absolute" | field: string, threshold: number |
"field_decreases_by_absolute" | field: string, threshold: number |
"field_matches_regex" | field: string, pattern: string |
"field_equals" | field: string, value: string | number | boolean |
"composite" | operator: "AND" | "OR", conditions: DiffPredicate[] (min 1) |
See Predicates for examples and semantics.
Delivery config
Discriminated by type. Each item in deliveries must be one of:
type | Required fields |
|---|---|
"webhook" | url: string (must be URL), optional headers: { [name]: value } (subject to Request headers rules) |
"slack" | webhookUrl: string (Slack incoming webhook URL) |
"discord" | webhookUrl: string (Discord webhook URL) |
"email" | to: string (email address) |
Request headers
request_headers (on the monitor, applied to outgoing scrape requests) and headers (on a webhook delivery, applied to outbound webhook calls) share the same validation:
- At most 20 headers.
- Header name: 1–256 chars, RFC 7230 token chars only (
[!#$%&'*+\-.^_|~0-9a-zA-Z]+`). - Header value: max 2048 chars, printable ASCII + tab (no CR/LF).
- Reserved names rejected:
host,content-length,connection,transfer-encoding,cookie.
Get a monitor
GET /v1/monitors/:id
Returns the monitor object:
{
"id": "uuid",
"user_id": "uuid",
"name": "string",
"url": "string",
"schedule_interval_seconds": 3600,
"extract_config": { "...": "..." },
"diff_predicate": { "...": "..." },
"deliveries": [],
"status": "active | paused | billing_paused | error | deleted",
"template_slug": "string | null",
"consecutive_failures": 0,
"fetch_mode": "auto | browser",
"request_headers": { "...": "..." },
"last_run_at": "ISO 8601 | null",
"next_run_at": "ISO 8601 | null",
"created_at": "ISO 8601"
}Update a monitor
PATCH /v1/monitors/:id
All create fields are patchable except template_slug (set once on creation). Same constraints and tier gates apply.
Delete a monitor
DELETE /v1/monitors/:id
Returns 204 No Content.
Pause / Resume a monitor
POST /v1/monitors/:id/pause — sets status: "paused".
POST /v1/monitors/:id/resume — sets status: "active" and reschedules an immediate run. Returns 403 FORBIDDEN if the monitor is billing_paused (locked because the user is over their plan's monitor cap).
Both take an empty JSON body ({}).
Trigger a manual run
POST /v1/monitors/:id/run
Empty body. Counts against the tier's runNowPerDay quota (free: 5, starter: 50, pro: 500, scale: 5000).
Returns { "run_id": "uuid", "queued": true }.
List runs for a monitor
GET /v1/monitors/:id/runs
Supports page and limit (same defaults as the global list).
Runs
Get a run
GET /v1/runs/:id
{
"id": "uuid",
"monitor_id": "uuid",
"user_id": "uuid",
"started_at": "ISO 8601",
"completed_at": "ISO 8601 | null",
"status": "running | success | error | skipped",
"extracted": { "...": "..." },
"diff": { "...": "..." },
"delivery_triggered": true,
"fetch_method": "static | browser | proxy | null",
"duration_ms": 1234,
"error_message": "string | null"
}List deliveries for a run
GET /v1/runs/:id/deliveries
Returns { "data": [Delivery, ...] } (not paginated — one run produces at most maxDeliveriesPerMonitor rows).
Replay all failed deliveries for a run
POST /v1/runs/:id/replay
Re-queues every failed or dead delivery on the run. Returns { "queued": <number> }.
Deliveries
List deliveries
GET /v1/deliveries
| Query param | Type | Default | Allowed values |
|---|---|---|---|
page | integer | 1 | >= 1 |
limit | integer | 20 | 1–100 |
status | string | all | "pending" | "success" | "failed" | "dead" |
Results are restricted to the tier's retention window (free: 14 days, starter: 180, pro: 365, scale: 730). The response also returns the retention envelope:
{
"data": [...],
"pagination": { "...": "..." },
"retention": {
"tier": "free",
"retention_days": 14,
"cutoff_at": "ISO 8601"
}
}Get a delivery
GET /v1/deliveries/:id
{
"id": "uuid",
"monitor_id": "uuid",
"user_id": "uuid",
"run_id": "uuid",
"delivery_type": "webhook | slack | discord | email",
"destination": "string",
"payload": { "...": "..." },
"status": "pending | success | failed | dead",
"attempts": 0,
"last_attempt_at": "ISO 8601 | null",
"next_attempt_at": "ISO 8601 | null",
"response_status": 200,
"response_body": "string | null",
"created_at": "ISO 8601"
}Replay a delivery
POST /v1/deliveries/:id/replay
Empty body. Resets the delivery to pending and re-queues it immediately. Returns { "queued": true }.
Templates
List templates
GET /v1/templates
| Query param | Type | Allowed values |
|---|---|---|
category | string | Filter to one category. Common values: developer, crypto, ecommerce, news, status, generic |
Each item:
{
"slug": "string",
"name": "string",
"description": "string",
"category": "string",
"audience": "developer | operator | both",
"example_url": "string | null",
"config": {
"extract": { "...": "..." },
"diff_predicate": { "...": "..." },
"schedule_interval_seconds": 3600
},
"created_at": "ISO 8601"
}Create monitor from template
POST /v1/monitors/from-template/:slug
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | no | Overrides the template's default name |
url | string | no | Overrides the template's example_url |
deliveries | array | no | Same shape as Delivery config |
The template's schedule_interval_seconds is clamped up to the caller's tier minimum if it would otherwise violate it.
Available template slugs: aws-status-page, crypto-price-coingecko, generic-css-selector, generic-full-page, generic-json-field, generic-llm-prompt, github-new-release, hacker-news-front-page, job-listings-css, npm-new-version, pypi-new-version, rss-new-item, shopify-product-price, sitemap-new-url, stock-price-yahoo.
API Keys
List API keys
GET /v1/keys
Returns { "data": [ApiKey, ...] }. The key_hash column is never returned — only key_prefix (the first 8 chars, safe to display).
Create API key
POST /v1/keys
| Field | Type | Required | Default | Constraints / Allowed values |
|---|---|---|---|---|
name | string | yes | — | 1–100 characters |
scopes | string[] | no | ["read"] | Each scope is a string. Recognized: "read", "write" |
expires_at | string | no | (no expiry) | ISO 8601 datetime |
Returns 201 Created:
{
"key": "vrd_...", // shown exactly once
"apiKey": { "id": "...", "name": "...", "scopes": [...], "expires_at": "..." }
}Rotate API key
POST /v1/keys/:id/rotate
| Field | Type | Required | Constraints |
|---|---|---|---|
grace_period_hours | integer | yes | 0–168 (0 = revoke old immediately; otherwise old key keeps working for N hours) |
Returns the same shape as create. The new key inherits the old key's remaining expiry window.
Delete API key
DELETE /v1/keys/:id
Returns 204 No Content.
Usage
Get usage summary
GET /v1/usage
{
"monitorsActive": 3,
"llmCallsThisMonth": 12,
"runNowToday": 1,
"tier": "free | starter | pro | scale",
"limits": {
"maxMonitors": 5,
"minIntervalSecs": 86400,
"retentionDays": 14,
"maxDeliveriesPerMonitor": 1,
"llmCallsPerMonth": 50,
"runNowPerDay": 5,
"proxyBytesPerMonth": 0
}
}Tier limits (reference)
| Limit | free | starter | pro | scale |
|---|---|---|---|---|
maxMonitors | 5 | 50 | 250 | 1,500 |
minIntervalSecs | 86,400 | 3,600 | 900 | 300 |
retentionDays | 14 | 180 | 365 | 730 |
maxDeliveriesPerMonitor | 1 | 3 | 10 | 25 |
llmCallsPerMonth | 50 | 500 | 5,000 | 25,000 |
runNowPerDay | 5 | 50 | 500 | 5,000 |
proxyBytesPerMonth | 0 | 52,428,800 | 524,288,000 | 5,368,709,120 |
OpenAPI
The full OpenAPI 3.1 spec is served by the API itself:
GET /api/openapi.json
You can also browse it interactively at /docs on the dashboard host.
Error responses
All errors follow this format:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Human-readable message",
"details": [],
"requestId": "string (optional)"
}
}| Code | HTTP Status | Meaning |
|---|---|---|
UNAUTHORIZED | 401 | Missing or invalid credentials |
FORBIDDEN | 403 | Insufficient permissions |
NOT_FOUND | 404 | Resource not found |
VALIDATION_ERROR | 422 | Invalid request body |
TIER_LIMIT_EXCEEDED | 429 | Plan limit reached |
RATE_LIMIT_EXCEEDED | 429 | Too many requests |
INTERNAL_ERROR | 500 | Server error |