Docs/Build & operate
Developer reference

API reference

Authenticate, select a tenant, and integrate with Probara's monitoring, alerting, incident, and administration APIs.

Base URL and conventions

The control-plane API is rooted at {API_ORIGIN}/api/v1. The API serves /healthz, /readyz, and /metrics on HTTP_PORT (8080 in the bundled deployments); its declared METRICS_PORT does not have a separate listener. Public rendered status pages are served by the separate status-page service.

Set an origin for examples
export PROBARA_API_ORIGIN='https://monitoring.example.com'

curl "$PROBARA_API_ORIGIN/healthz"
curl "$PROBARA_API_ORIGIN/readyz"
ConventionBehavior
Content typeSend application/json for JSON request bodies; import endpoints also accept supported file content
IdentifiersResources use UUID-shaped IDs unless the endpoint uses a public slug or capability token
TimesUse RFC 3339 timestamps for filters and scheduled fields
Tenant scopeResolved from an admin membership selection or permanently from the API key
DeletionMany operational resources are soft-deleted first and cleaned asynchronously
ErrorsMost application handlers return JSON error information, but a few low-level validation paths can return plain HTTP error text

Authentication

Administrator session
Login establishes an HTTP-only access cookie and a rotating refresh session. It is the credential used by the web application and can switch among allowed tenants.
API key
Send Authorization: Bearer pk_<secret>. The key has read or write scope and is bound to its creation tenant.
Push token
Used only in /api/v1/push/{token}. No cookie or bearer key is required because possession of the URL token authorizes a heartbeat.
MCP access token
Bearer pbm_at_…, issued to an AI agent by the MCP sign-in. Valid only on /api/v1/mcp: every tool call there runs as one of the routes below with the same checks. A direct REST call with it returns 401.
API-key request
curl \
  -H 'Authorization: Bearer pk_<secret>' \
  -H 'Accept: application/json' \
  "$PROBARA_API_ORIGIN/api/v1/monitors?page=1&page_size=20"
Public authentication routePurpose
POST /api/v1/auth/loginLocal username/password login
POST /api/v1/auth/refreshRotate and refresh an admin session
POST /api/v1/auth/logoutRevoke the current refresh session and clear cookies
GET /api/v1/auth/meRead the current session user when present
GET /api/v1/auth/oidc/statusReport OIDC availability
GET /api/v1/auth/oidc/startBegin authorization-code + PKCE login
GET /api/v1/auth/oidc/callbackComplete the provider callback
GET /api/v1/users/bootstrap/statusCheck whether first-user bootstrap remains available
POST /api/v1/users/bootstrap/firstCreate the one-time first superadministrator

Tenant selection and permissions

For an administrator cookie, send X-Tenant-ID: <tenant-id> to select an allowed membership. A supported tenant_id query fallback can select context where middleware permits it. API keys ignore caller-selected tenant values and remain pinned to one tenant.

Administrator-session tenant header
curl \
  --cookie cookie-jar.txt \
  -H 'X-Tenant-ID: <tenant-id>' \
  "$PROBARA_API_ORIGIN/api/v1/auth-context"
PrincipalReadsMutations
Tenant viewer / read API keyTenant dataOnly approved compute-only POST diagnostics
Tenant editor / write API keyTenant dataNormal operational writes; not tenant-admin-only governance
Tenant adminTenant data and auditAll tenant writes, API-key management, and tenant settings
Platform superadminAny selected tenant plus platform usersPlatform user administration and tenant operations

Compute-only POST exceptions include /monitors/test, /monitors/import/preview, /monitors/dependency-suggestions, /mesh/probe, and /ai-settings/test. They do not persist monitor/result/graph state. Channel test sends a real outbound notification and therefore is not read-only. The AI label routes persist operator verdicts, so they are writes: viewers and read keys get HTTP 403.

Pagination and common filters

Collection APIs generally accept 1-based page and page_size. The common maximum is 100; locations allow up to 200, and dashboard recent-item limits are capped at 50. Use returned total/page metadata where provided rather than inferring completion from a short page.

ResourceImportant filters
Monitorstag (repeatable where supported), enabled, page, page_size (default 20, max 100); newest first by creation time, ties broken by id, so pages never overlap
Monitor resultslimit, optional RFC 3339 since
Batch monitor resultsids (comma-separated or repeated, up to 100), limit per monitor (default 20, max 100)
Monitor analyticsrange: 1h, 6h, 24h, 7d, 30d, 90d, 365d
Alertsstatus, monitor_id, since, q, suppressed, sort, dir, page, page_size (max 100)
Maintenancestatus, monitor_id, page, page_size
Mesh historysource, target, and hours
Dashboardrange, repeatable tag, and bounded recent-item limits
Audit logaction, outcome, actor_id, RFC 3339 from / to, pagination

Monitor endpoints

Method and pathPurpose
POST /api/v1/monitorsCreate a monitor
GET /api/v1/monitorsList tenant monitors
POST /api/v1/monitors/testExecute a supplied config without persistence or alerting
GET /api/v1/monitors/{id}Get full monitor detail
PATCH /api/v1/monitors/{id}Update monitor fields/config
DELETE /api/v1/monitors/{id}Soft-delete a monitor
POST /api/v1/monitors/{id}/runQueue a persisted on-demand check (refused with 400 for group, push and agent monitors, which never run a check of their own)
GET /api/v1/monitors/{id}/resultsList raw check results
GET /api/v1/monitors/results/batch?ids=...&limit=20The newest results for up to 100 monitors in one request, plus each group's direct member ids (see below)
GET /api/v1/monitors/{id}/metrics-snapshots?range=24hPer-check metrics_data over a range, newest snapshot per bucket (≤720), clamped to 30-day raw retention
GET /api/v1/monitors/{id}/analyticsGet summary, series, latency, downtime, and coverage
DELETE /api/v1/monitors/{id}/historyDelete the monitor's stored result history
GET /api/v1/monitors/{id}/artifacts/screenshot?path=...Retrieve an authorized synthetic-browser screenshot path
POST /api/v1/monitors/{id}/snoozeCreate a one-monitor snooze by duration or end time
Create an HTTP monitor
{
  "name": "Public API",
  "type": "http",
  "config": {
    "url": "https://api.example.com/health",
    "method": "GET",
    "expected_status_classes": ["2xx"],
    "json_assertions": [
      {"path": "status", "op": "equals", "value": "ok"}
    ],
    "max_latency_ms": 1500
  },
  "interval_seconds": 60,
  "timeout_seconds": 15,
  "enabled": true,
  "tags": ["api", "production"],
  "consecutive_failures_threshold": 2,
  "notification_mode": "default",
  "location_ids": [],
  "location_quorum": 1
}

GET /monitors/results/batch answers {"limit", "results": {monitor_id: [...]}, "members": {group_id: [member_id, ...]}}. Each results list is what GET /monitors/{id}/results?limit=N returns for that monitor: a group's list merges its nested members' newest results under a derived group-status row, and members lists a group's direct members (by name). Ids that are not live monitors of the workspace are left out rather than failing the request; more than 100 ids, a malformed id, or a limit outside 1-100 is a 400. Raw results are kept 30 days, so a monitor that has not run for longer than that comes back with an empty list (here and on the per-monitor endpoint alike). Viewers and read-scope keys may call it.

Monitor relationships, bulk, and portability

Method and pathPurpose
GET /api/v1/monitors/{id}/membersList group members
POST /api/v1/monitors/{id}/membersAdd monitors to a group
DELETE /api/v1/monitors/{id}/membersRemove supplied monitors from a group
GET /api/v1/monitors/{id}/dependenciesList upstream dependencies
POST /api/v1/monitors/{id}/dependenciesCreate an upstream edge
DELETE /api/v1/monitors/{id}/dependencies/{dependsOnId}Remove an upstream edge
GET /api/v1/monitors/{id}/dependentsList downstream dependents
GET /api/v1/monitors/dependency-graphGet all participating nodes and directed edges
POST /api/v1/monitors/dependency-suggestionsCompute advisory AI suggestions without saving edges. 503 not_configured when local AI is off; 503 unavailable with Retry-After while the service is busy or restarting
POST /api/v1/monitors/dependency-suggestions/labelWrite; record an operator verdict on a suggestion as an AI label
POST /api/v1/monitors/bulk/alertingUpdate notification routing for selected monitors
POST /api/v1/monitors/bulk/deleteSoft-delete selected monitors
GET /api/v1/monitors/exportExport a versioned YAML configuration bundle
POST /api/v1/monitors/import/previewParse, map, and validate without creating resources
POST /api/v1/monitors/importExecute import and return per-row outcomes

Dependency creation rejects self-reference and transitive cycles with HTTP 409. Export/import is not a universal secret-preserving round trip: masked fields and newer monitor types require explicit preview and remediation. Import preview reports a schema for recognized sources (portable_monitor_export, uptime_kuma_export), per-row warnings, and skipped_rows naming source records it declined to translate.

Locations, mesh, and maintenance

Method and pathPurpose
POST /api/v1/locationsCreate a private location and credential
GET /api/v1/locationsList tenant locations
GET /api/v1/locations/{id}Get location detail
PATCH /api/v1/locations/{id}Update location metadata/state
DELETE /api/v1/locations/{id}Delete and detach a location
GET /api/v1/locations/{id}/deployGenerate credential-scoped deployment information
GET /api/v1/meshRead current directional mesh topology/state
GET /api/v1/mesh/historyRead filtered edge history
POST /api/v1/mesh/probeRun a compute-only immediate directional diagnostic
POST /api/v1/maintenance-windowsCreate scheduled maintenance
GET /api/v1/maintenance-windowsList/filter maintenance
GET /api/v1/maintenance-windows/{id}Get a maintenance window
PATCH /api/v1/maintenance-windows/{id}Update a maintenance window
DELETE /api/v1/maintenance-windows/{id}Delete a maintenance window
Create maintenance
{
  "title": "Database upgrade",
  "description": "Planned primary database maintenance",
  "starts_at": "2026-08-01T22:00:00Z",
  "ends_at": "2026-08-01T23:30:00Z",
  "monitor_ids": ["<monitor-id>"]
}

SLA endpoints

EndpointPurpose
GET /api/v1/slasList SLAs with the running period's status; monitor_id keeps SLAs covering that monitor
POST /api/v1/slasCreate an SLA
GET /api/v1/slas/{id}Get an SLA
PATCH /api/v1/slas/{id}Partially update an SLA; monitor_ids replaces the explicit monitor set
DELETE /api/v1/slas/{id}Delete an SLA and its issued reports
GET /api/v1/slas/{id}/reportLive report for period (2026-09, 2026-Q3, 2026-W39), from/to local dates, or the running period; format=json|csv|pdf returns a download
GET /api/v1/slas/{id}/reportsList issued (frozen) reports
POST /api/v1/slas/{id}/reportsIssue a closed period's report ({"period": "2026-09"}, default the last closed period); 409 when already issued
GET /api/v1/sla-reports/{id}An issued report as frozen; format=json|csv|pdf returns a download
Create an SLA
{
  "name": "Checkout platform",
  "target_pct": 99.9,
  "aggregation": "serial",
  "period": "monthly",
  "timezone": "Europe/Paris",
  "degraded_counts_as_down": false,
  "tags": ["checkout"],
  "monitor_ids": ["<monitor-id>"]
}

Report semantics, composite rules and limits are covered in SLA reporting.

Dashboard endpoints

Method and pathResponse focus
GET /api/v1/dashboard/overviewStats, trend, activity, platform health, problems, recent failures/alerts, tags
GET /api/v1/dashboard/summaryCurated tag-group service summary and ungrouped monitors
GET /api/v1/dashboard/problem-monitorsMonitors needing attention
GET /api/v1/dashboard/recent-failuresBounded recent failing results
GET /api/v1/dashboard/recent-alertsBounded recent alert records
GET /api/v1/dashboard/group-sparklineDeferred time series for one dashboard group

Overview ranges are 1h, 24h, 7d, 30d, 90d, and 365d, defaulting to 24h. Dashboard group rows are tag-derived presentation, not group-monitor resources. Problem monitors are ordered by current state (down, degraded, unknown, up), then failed checks, before the limit applies, so a monitor that is down now is never cut. A group's attention_count counts members down or degraded now; failures earlier in the range show only in its uptime.

Alert and notification endpoints

Method and pathPurpose
GET /api/v1/alertsPaginated/filterable alert list (status, monitor_id, since — alerts open at any point after it, so still-open alerts always match — suppressed=true|false, and q, a case-insensitive search of the subject: the monitor name or either location of a mesh alert, or an exact alert ID, up to 200 characters), ordered by sort (triggered_at, the default; status, ranked active, acknowledged, resolved; monitor, the subject name, case-insensitive; duration, counting open alerts to now) and dir (asc/desc; defaults to desc for triggered_at and duration, so newest and longest first, and asc otherwise: lifecycle rank for status, A-Z for monitor). Under a single status filter a status sort is the default newest-first order. Ties read newest first, then by ID, so pages never overlap; any other value is HTTP 400. Items carry suppression_reason and impacted_count when dependency suppression applies, and triage when the alert has an automatic triage
GET /api/v1/alerts/recentRecent alerts with bounded limit
GET /api/v1/alerts/streamAuthenticated server-sent event stream
GET /api/v1/alerts/{id}Alert detail; a labelable triage also carries label_context
POST /api/v1/alerts/{id}/acknowledgeMark an open condition acknowledged
POST /api/v1/alerts/{id}/resolveManually close an alert lifecycle
POST /api/v1/alerts/bulk/acknowledgeWrite; acknowledge many alerts, body {"ids": [...]} (1-500). Never all-or-nothing: HTTP 200 with items (the alerts acknowledged, in their new state) and failed ({id, reason}, reason not_found (missing, another tenant's, or its monitor was deleted), already_acknowledged or already_resolved). Each requested alert gets its own alert.acknowledge audit event, as the single endpoint records it, with details.bulk
POST /api/v1/alerts/bulk/resolveWrite; resolve many open alerts, same body and response shape (reason not_found or already_resolved), one alert.resolve audit event per alert
POST /api/v1/alerts/{id}/triage/labelWrite; confirm or correct a ready triage as an AI label
GET /api/v1/alert-channelsList configured channels
POST /api/v1/alert-channelsCreate a plugin-backed channel
GET /api/v1/alert-channels/{id}Read channel with protected config masked
PATCH /api/v1/alert-channels/{id}Update config or active state
DELETE /api/v1/alert-channels/{id}Delete a channel
POST /api/v1/alert-channels/{id}/testSend a real test notification
GET /api/v1/alert-channel-pluginsList channel plugin form manifests
GET /api/v1/alert-channel-plugins/{type}Get one plugin manifest
GET /api/v1/notification-settingsRead tenant defaults, reminders, anomaly and auto-incident settings
PUT /api/v1/notification-settingsReplace tenant notification settings

Alert reads (list, detail, recent alerts, and the acknowledge and resolve responses) and the alerts[] of GET /incidents/{id} include a read-only triage object when the alert has a triage row. The object is omitted when there is no row, and stream events never carry it. Fields that do not apply are null: id, status (pending, ready, failed, skipped), hypothesis (catalogue key) and title, confidence (low, medium, high) and summary when ready, skip_reason (resolved, cooldown, not_modeled, disabled) when skipped, error_message (a fixed string) when failed, created_at (claim time), completed_at, labelled, and labelable (ready and at least one decision was sent to the model). Only GET /alerts/{id} adds label_context, in the same shape as an incident analysis's, when the triage is labelable.

Incident endpoints

Method and pathPurpose
GET /api/v1/incidentsList incidents (paginated; total counts all, open_total counts unresolved)
POST /api/v1/incidentsCreate an incident
GET /api/v1/incidents/{id}Get full incident context
PATCH /api/v1/incidents/{id}Update metadata
POST /api/v1/incidents/{id}/stateTransition lifecycle state
POST /api/v1/incidents/{id}/timelineAdd an internal note, public update, or supported timeline event
POST /api/v1/incidents/{id}/alertsAttach an alert
DELETE /api/v1/incidents/{id}/alerts/{alertId}Detach an alert
POST /api/v1/incidents/{id}/monitorsAttach a monitor
DELETE /api/v1/incidents/{id}/monitors/{monitorId}Detach a monitor
PUT /api/v1/incidents/{id}/status-pages/{statusPageId}Publish incident to one status page
DELETE /api/v1/incidents/{id}/status-pages/{statusPageId}Unpublish from one status page
POST /api/v1/incidents/{id}/ai-analysisQueue asynchronous local AI analysis; a queueing failure records the run as failed
GET /api/v1/incidents/{id}/ai-analysisRead pending, ready, or failed analysis state
POST /api/v1/incidents/{id}/ai-analysis/labelWrite; confirm or correct a ready analysis as an AI label

A ready analysis that can be labelled also carries label_context on the analysis GET and on GET /incidents/{id}: catalogue_version, the incident-level predicted key, the selectable choices (key, title), one entry per monitor decision (id, monitor_id, monitor_name, which is null once the monitor is purged, and predicted), and the current label or null. It is omitted for pending, failed and legacy analyses, analyses that sent no decisions, and analyses completed before labels existed. The analysis GET and GET /incidents/{id} also add monitor_names, a map from each monitor ID cited in metadata (evidence references and incomplete-evidence notes) to the monitor's current name; purged monitors are absent, and the map is omitted when nothing resolves.

AI label endpoints

Method and pathWho and purpose
POST /api/v1/incidents/{id}/ai-analysis/labelEditor, admin, superadmin or write key; label an incident analysis
POST /api/v1/alerts/{id}/triage/labelEditor, admin, superadmin or write key; label an alert's automatic triage
POST /api/v1/monitors/dependency-suggestions/labelEditor, admin, superadmin or write key; label a dependency suggestion
POST /api/v1/ai-labels/exportTenant admin or superadmin session only (no API key); download training rows

Bodies are strict JSON up to 64 KiB, and unknown fields are rejected with validation_error, so evidence can never be supplied: the server copies it from the stored decision snapshot or recomputes it. Labelling never calls the model and works with local AI disabled. A label is one row per analysis, triage or unordered monitor pair; sending again replaces the verdict and answers (latest wins). Every label route returns the label: subject_type, subject_id (analyses and triages only), verdict, incident_label (for a triage, its alert-level cause), answers keyed by opaque decision ID (label, plus source: operator, confirmed or propagated), catalogue_version, model_version, labeled_by (null for API keys) and labeled_at.

Incident analysis label
{
  "analysis_id": "<optional; defaults to the newest ready analysis>",
  "verdict": "corrected",
  "incident_label": "dns_failure",
  "answers": { "<decision id>": "insufficient_evidence" }
}

verdict is confirmed or corrected. Confirming keeps every decision's shown answer and accepts incident_label only when it equals the prediction; correcting requires an incident_label from the analysis's choices. Optional answers override single decisions. Errors: 400 validation_error (bad verdict, a cause or override that is not a choice of that decision, unknown decision ID), 404 not_found (incident or analysis_id not in this tenant), 409 analysis_not_ready (pending, failed, or no ready analysis), 409 analysis_not_labelable (legacy, no-decision, or pre-label analysis).

Alert triage label
{
  "verdict": "corrected",
  "label": "dns_failure",
  "answers": { "<decision id>": "insufficient_evidence" }
}

The triage label works like the incident analysis label, with label in place of incident_label. Confirming accepts label only when it equals the triage's hypothesis, and correcting requires one of its choices. The label is stored with subject type alert_triage, and its evidence comes from the triage's stored decision snapshot. Errors: 400 validation_error (bad verdict, a cause or override that is not a choice, unknown decision ID, unknown fields), 404 not_found (alert not in this tenant), 409 triage_not_ready (no triage, or one that is pending, failed or skipped), 409 triage_not_labelable (the triage sent no decisions).

Dependency suggestion label
{
  "monitor_id": "<dependent monitor>",
  "depends_on_id": "<suggested upstream>",
  "verdict": "rejected",
  "corrected": "reversed"
}

verdict is confirmed or rejected; corrected is allowed only with rejected and is reversed, shared_upstream, insufficient_evidence or no_dependency (the default). Either orientation addresses the same pair. Errors: 400 validation_error (identical or malformed IDs, bad verdict), 404 not_found (a monitor is not a live monitor of this tenant), 409 pair_evidence_unavailable (the pair is no longer a candidate: disabled, a group, outside the 250-monitor set, or no shared signal left).

Export takes an optional body with since and until (RFC 3339, a half-open window on the label's last update) and subject_types (incident_analysis, alert_triage, dependency_pair); an empty body exports everything, and an inverted window or unknown type is a validation_error. It returns application/x-ndjson as probara-ai-labels-YYYYMMDD.jsonl with X-Probara-Export-Rows and X-Probara-Export-Skipped headers: one training row per decision, and rows whose stored evidence fails validation are skipped and counted. Triage rows share a split group with the earliest incident that links the alert, or form their own group when none does. More than 5,000 labels or 20,000 rows returns 400 export_too_large; narrow the window. Rows contain no identifiers (see security).

Status page endpoints

Method and pathPurpose
POST /api/v1/status-pagesCreate a page
GET /api/v1/status-pagesList pages
GET /api/v1/status-pages/{id}Get page configuration
PATCH /api/v1/status-pages/{id}Update page and settings
DELETE /api/v1/status-pages/{id}Delete a page
GET /api/v1/status-pages/{id}/templateRead published/draft/version state
DELETE /api/v1/status-pages/{id}/templateReset to the built-in template as a new version
GET /api/v1/status-pages/{id}/template/defaultExport current built-in source
GET /api/v1/status-pages/{id}/template/versions/{version}/sourceRead archived version source
PUT /api/v1/status-pages/{id}/template/draftValidate and save the one draft
DELETE /api/v1/status-pages/{id}/template/draftDiscard draft
POST /api/v1/status-pages/{id}/template/publishPublish draft and archive current source
POST /api/v1/status-pages/{id}/template/revertRepublish archived source as a new version
Template-library routePurpose
GET /api/v1/status-page-templatesList tenant template metadata
POST /api/v1/status-page-templatesCreate a validated library entry
GET /api/v1/status-page-templates/{templateId}Get entry metadata
GET /api/v1/status-page-templates/{templateId}/sourceRetrieve source
PATCH /api/v1/status-page-templates/{templateId}Rename/update source
DELETE /api/v1/status-page-templates/{templateId}Delete library entry

The separate status-page service exposes unauthenticated GET /public/status/{slug}, /data, and /stream, plus draft preview. Do not use those public routes to mutate configuration.

Agent and push endpoints

Method and pathAuthorization and purpose
POST /api/v1/otlp/v1/metricsTenant write API key (Bearer) plus X-Probara-Agent-Id; OTLP/HTTP metric ingest for collector agents
POST /api/v1/agent/metricsRetired legacy-agent ingest (sunset 18 Nov 2026): an authenticated request gets 410 Gone, a JSON error naming the OTLP endpoint, and a Link: </api/v1/otlp/v1/metrics>; rel="successor-version" header. The body is not read and nothing is stored
GET /api/v1/monitors/{id}/agent/installAuthenticated; generated install command/information incl. collector_config and collector_version
GET /api/v1/monitors/{id}/agent/config.yamlAuthenticated; raw collector YAML for one platform (linux/darwin/windows)
GET /api/v1/monitors/{id}/agent/install/script.shAuthenticated Unix installer
GET /api/v1/monitors/{id}/agent/install/script.ps1Authenticated Windows installer
GET /api/v1/monitors/{id}/agent/uninstall/script.shAuthenticated Unix uninstaller
GET /api/v1/monitors/{id}/agent/uninstall/script.ps1Authenticated Windows uninstaller
GET /api/v1/monitors/{id}/metrics/seriesAuthenticated; discover stored metric series for an agent monitor
POST /api/v1/monitors/{id}/metrics/queryAuthenticated (read-only despite POST, viewer-accessible); batch range queries — ≤12 queries, ≤50 series and ≤2,000 points per series per response, automatic raw-vs-rollup selection for ranges over 48 h or steps ≥1 h
GET /api/v1/monitors/{id}/push/infoAuthenticated generated push URL/config information
GET /api/v1/push/{token}Public capability token; success heartbeat plus query metrics
POST /api/v1/push/{token}Public capability token; status/error plus arbitrary metric values

Discovery endpoints

Method and pathAuthorization and purpose
GET/POST /api/v1/discovery/integrationsAuthenticated; list or create a Kubernetes discovery integration. Create (write access) returns the collector token once; the first namespace becomes home_namespace. An integration carries namespaces (active), home_namespace, pending_namespaces (always an array) and pending_status (always an object of {reason, checked_at} per pending namespace with a recorded skip)
PATCH /api/v1/discovery/integrations/{id}Write access; any of name (1–100 characters), location_id (an enabled location, null clears, absent keeps), add_namespaces, remove_namespaces (each at most 20). Added namespaces become pending; the home namespace cannot be removed; at most 20 namespaces active and pending together. Namespaces change only as this delta: an unknown field such as namespaces is a 400. Returns 200 with the integration (no token); an edit that changes nothing returns 200 and is not audited. See Change namespaces
GET /api/v1/discovery/integrations/{id}/namespacesAuthenticated; items: home first, then the other active namespaces, then pending ones, each with namespace, state (active/pending), home, reason and checked_at (pending with a recorded skip only), services (stored, present or missing) and linked_services (linked to a monitor that is not deleted), counted whatever the state
DELETE /api/v1/discovery/integrations/{id}Write access; removes inventory and links, keeps monitors
POST /api/v1/discovery/integrations/{id}/rotate-tokenWrite access; returns a new collector token and invalidates the old one
GET /api/v1/discovery/integrations/{id}/coverageAuthenticated; page, q (service or namespace), gaps=true, sort=name|gaps apply across all services in the integration's active namespaces; summary is unfiltered. summary.missing counts resource_missing only, summary.missing_grace counts resource_missing_grace. inventory_stale is reported once at the top level and prefixed to every row's gaps, but the summary counts, gaps=true and sort=gaps ignore it
POST /api/v1/discovery/services/{id}/approveWrite access; create and link a monitor from a suggestion. Returns {monitor_id, created} with 200; a repeat for an already linked suggestion returns that monitor with created: false, writes nothing and is not audited
POST /api/v1/discovery/services/{id}/links, DELETE …/links/{monitorID}Write access; explicitly link or unlink an existing monitor
GET /api/v1/discovery-collectors/{id}/config, POST …/snapshotIntegration-scoped collector token only (Bearer); never valid on other routes. Config returns {namespaces, pending, revision}; a snapshot reports pending namespaces it could not scan in skipped ({namespace: reason})

POST /api/v1/discovery-collectors/{id}/snapshot returns 204 when the snapshot is applied, 401 for a missing or malformed bearer, 404 for an unknown integration or wrong token, 400 for an invalid body (unknown JSON fields are ignored, so a newer collector still syncs; known fields are fully validated), 409 for a stale or replayed revision (no Retry-After), 409 with Retry-After: 10 when it scanned a namespace that was removed meanwhile (not counted against the rate limit), 429 rate_limited with Retry-After (whole seconds) when a snapshot was applied, or refused with 422, less than 60 seconds earlier, and 422 inventory_limit when the integration would store more than 10,000 resources after reconciling its active namespaces (pending namespaces that would exceed it are left pending as inventory_limit and the snapshot is applied without them). A refused snapshot leaves the inventory, revision and last-seen time unchanged; a 422 still counts against the rate limit, since it did the reconciliation work. The token and the rate limit are checked before the body of up to 4 MiB is read.

Administration endpoints

Method and pathRequired role and purpose
GET /api/v1/auth-contextAuthenticated; effective credential, tenant, role, and scope
GET /api/v1/tenantsAdmin session; list membership-filtered tenants or all for superadmin
GET /api/v1/tenant-settingsAuthenticated; read retention and dashboard tags
PATCH /api/v1/tenant-settingsTenant admin; update retention/dashboard tags
GET /api/v1/api-keysAuthenticated tenant context; list safe key metadata
POST /api/v1/api-keysTenant admin; create and return secret once
DELETE /api/v1/api-keys/{id}Tenant admin; revoke
GET /api/v1/audit-logTenant admin; filtered, sortable audit records
GET /api/v1/audit-log/actionsTenant admin; distinct action catalog
GET /api/v1/ai-settingsAuthenticated; workspace switch, installation switch, cached local-service readiness, and the auto-triage settings (auto_triage_enabled, triage_delay_seconds, triage_cooldown_seconds, triage_max_per_hour, triage_notifications_enabled)
PUT /api/v1/ai-settingsAuthorized write; partial update of enabled and the five auto-triage fields (at least one; ranges in AI settings); unknown fields are rejected
POST /api/v1/ai-settings/testFresh local-service readiness probe; refreshes this replica's readiness cache
GET /api/v1/usersSuperadmin; list platform users
POST /api/v1/usersSuperadmin; create user and memberships
GET /api/v1/users/{id}Superadmin; user detail
PATCH /api/v1/users/{id}Superadmin; update role/auth/membership fields
DELETE /api/v1/users/{id}Superadmin; delete subject to last-admin/self safeguards
GET /api/v1/oidc-group-mappingsSuperadmin; list OIDC group→role mappings, groups seen at past SSO logins, and groups-claim config facts
POST /api/v1/oidc-group-mappingsSuperadmin; map an IdP group to a tenant role or platform superadmin
PATCH /api/v1/oidc-group-mappings/{id}Superadmin; change a mapping's role and/or display label
DELETE /api/v1/oidc-group-mappings/{id}Superadmin; remove a mapping

Server-sent events

GET /api/v1/alerts/stream is an authenticated tenant alert stream. The public status-page service exposes a different slug-scoped stream at /public/status/{slug}/stream. Use a native EventSource-compatible client where cookie authentication is required, or a streaming HTTP client that can attach the bearer key.

Inspect an alert stream
curl -N \
  -H 'Authorization: Bearer pk_<secret>' \
  "$PROBARA_API_ORIGIN/api/v1/alerts/stream"
  • Reconnect with backoff after network or proxy interruption.
  • Treat events as refresh/invalidation signals and refetch durable resource state.
  • Keep proxy buffering disabled for the streaming route.
  • Do not assume the stream is a replayable audit log.

Build a safe client

  • Set explicit HTTP timeouts and retry only operations whose idempotency you understand.
  • Retry GET requests with bounded exponential backoff; do not blindly retry create, run-now, import execution, notification tests, or incident timeline writes.
  • Use import preview before import execution and monitor test before saving complex check configuration.
  • Respect 401, 403, 404, 409, 410, 422, and rate/transport failures as distinct conditions.
  • Send ***, or leave the field out, to keep a masked designated secret field on a monitor; send an empty string when you deliberately want to clear it. Notification channel configs differ: an empty string also keeps the stored secret, and null clears an optional one (a required one is then rejected by validation).
  • Page collections and retain returned coverage metadata for analytics.
  • Store full API keys only in a secret manager; the API returns them once.
  • Log resource IDs and status codes without logging bearer keys, push tokens, installer output, WebSocket headers, connection strings, or webhook secrets.