All endpoints are prefixed with /api. All responses are JSON (Content-Type: application/json).
Mutating requests (POST, PUT, DELETE) require the X-CSRF-Token header. The token value is read from the csrf-token cookie, which is set by the server on the first response.
Most endpoints require a valid session. Authenticate via OIDC login flow or local auth (POST /api/auth/login). The session is stored in a cookie (deps-dashboard.sid).
Common error responses:
| Status | Meaning |
|---|---|
401 |
Not authenticated — session missing or expired |
403 |
Forbidden — insufficient role or CSRF token missing/invalid |
404 |
Resource not found |
409 |
Conflict — duplicate resource |
429 |
Rate limited — retry after the Retry-After header value |
Returns server health status. No authentication required. Exempt from HTTPS redirect and rate limiting.
curl http://localhost:3001/api/health{ "status": "ok" }Rate limited: 20 requests/minute per IP on all /api/auth endpoints.
Returns the current authentication mode. No authentication required.
curl http://localhost:3001/api/auth/mode{ "mode": "local" }mode is either "oidc" or "local".
Initiates the OIDC login flow. Redirects the browser to the OIDC provider. Only used in OIDC mode.
| Query Param | Type | Description |
|---|---|---|
returnTo |
string | Optional. URL to redirect to after login (default: /) |
# Browser redirect — not typically called via curl
curl -v "http://localhost:3001/api/auth/login?returnTo=/services"Returns 302 redirect to the OIDC provider's authorization endpoint.
OIDC callback endpoint. Exchanges the authorization code for tokens, creates or updates the user, and redirects to the frontend. Not called directly by clients.
Local auth login. Only available when LOCAL_AUTH=true.
curl -X POST http://localhost:3001/api/auth/login \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{"email": "admin@example.com", "password": "changeme123"}'Request body:
{
"email": "string (required)",
"password": "string (required)"
}Success response (200):
{
"id": "uuid",
"email": "admin@example.com",
"name": "Admin User",
"role": "admin"
}Error responses:
| Status | Body |
|---|---|
401 |
{ "error": "Invalid email or password" } |
404 |
Returned when not in local auth mode |
Destroys the session and returns a redirect URL. Requires authentication.
curl -X POST http://localhost:3001/api/auth/logout \
-H "X-CSRF-Token: <token>" \
-b cookies.txtResponse (200):
{ "redirectUrl": "/login" }In OIDC mode, redirectUrl may point to the OIDC provider's end-session endpoint.
Returns the current user's profile, team memberships, and permissions. Requires authentication.
curl http://localhost:3001/api/auth/me -b cookies.txtResponse (200):
{
"id": "uuid",
"email": "user@example.com",
"name": "User Name",
"role": "admin",
"is_active": true,
"teams": [
{
"team_id": "uuid",
"role": "lead",
"team": { "id": "uuid", "name": "Platform", "description": "Platform team" }
}
],
"permissions": {
"canManageUsers": true,
"canManageTeams": true,
"canManageServices": true
}
}List services. Non-admin users see only services belonging to their teams. Admin users see all services.
| Query Param | Type | Description |
|---|---|---|
team_id |
uuid | Optional. Filter by team (validated against membership for non-admins) |
curl http://localhost:3001/api/services -b cookies.txtResponse (200): Array of service objects.
[
{
"id": "uuid",
"name": "Payment Service",
"team_id": "uuid",
"team_name": "Platform",
"health_endpoint": "https://payment-svc/health",
"metrics_endpoint": null,
"schema_config": null,
"poll_interval_ms": 30000,
"is_active": 1,
"last_poll_success": 1,
"last_poll_error": null,
"dependency_count": 3,
"healthy_count": 2,
"unhealthy_count": 1,
"created_at": "2024-01-15T10:00:00.000Z"
}
]Get a single service with its dependencies and dependent reports. Non-admin users must be a member of the service's owning team.
curl http://localhost:3001/api/services/<service-id> -b cookies.txtResponse (200):
{
"id": "uuid",
"name": "Payment Service",
"team_id": "uuid",
"team": { "id": "uuid", "name": "Platform", "description": "..." },
"health_endpoint": "https://payment-svc/health",
"metrics_endpoint": null,
"schema_config": null,
"poll_interval_ms": 30000,
"is_active": 1,
"last_poll_success": 1,
"last_poll_error": null,
"created_at": "2024-01-15T10:00:00.000Z",
"dependencies": [
{
"id": "uuid",
"name": "postgres-main",
"canonical_name": "PostgreSQL Primary",
"type": "database",
"is_healthy": true,
"latency_ms": 12,
"error_message": null,
"effective_contact": "{\"email\":\"db-team@example.com\",\"slack\":\"#db-support\"}",
"effective_impact": "Critical — primary database"
}
],
"dependent_reports": [
{
"service_id": "uuid",
"service_name": "API Gateway",
"is_healthy": true,
"dependency_name": "payment-service"
}
]
}Create a new service. Requires team lead role on the specified team, or admin.
curl -X POST http://localhost:3001/api/services \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{
"name": "Payment Service",
"team_id": "<team-uuid>",
"health_endpoint": "https://payment-svc/health",
"poll_interval_ms": 30000
}'Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Service name |
team_id |
uuid | Yes | Owning team ID |
health_endpoint |
url | Yes | Health check URL (SSRF-validated) |
metrics_endpoint |
url | No | Metrics URL |
schema_config |
object | No | Custom schema mapping (see Health Endpoint Spec) |
poll_interval_ms |
number | No | Poll interval in ms (default: 30000, min: 5000, max: 3600000) |
Response (201): The created service object.
Error responses:
| Status | Reason |
|---|---|
400 |
Validation error (invalid URL, poll interval out of range, etc.) |
403 |
Not a team lead of the specified team |
Update a service. Requires team lead role on the service's owning team, or admin.
curl -X PUT http://localhost:3001/api/services/<service-id> \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{ "name": "Payment Service v2", "poll_interval_ms": 60000 }'Request body: Same fields as create (all optional for updates).
Response (200): The updated service object.
Delete a service. Requires team lead role on the service's owning team, or admin.
curl -X DELETE http://localhost:3001/api/services/<service-id> \
-H "X-CSRF-Token: <token>" \
-b cookies.txtResponse: 204 No Content
Trigger an immediate health poll for a service. Requires team membership (any role) on the service's owning team, or admin.
curl -X POST http://localhost:3001/api/services/<service-id>/poll \
-H "X-CSRF-Token: <token>" \
-b cookies.txtResponse (200):
{
"success": true,
"dependencies_updated": 3,
"status_changes": 1,
"latency_ms": 245,
"error": null
}Test a schema mapping against a live health endpoint URL. Does not store anything. SSRF-protected.
Requires authentication and team lead role on any team, or admin.
curl -X POST http://localhost:3001/api/services/test-schema \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{
"url": "https://example.com/health",
"schema_config": {
"root": "checks",
"fields": {
"name": "name",
"healthy": { "path": "status", "equals": "UP" },
"latency": "responseTime"
}
}
}'Request body:
| Field | Type | Required | Description |
|---|---|---|---|
url |
url | Yes | Health endpoint URL to test (SSRF-validated) |
schema_config |
object/string | Yes | Schema mapping config (object or JSON string) |
Response (200):
{
"success": true,
"dependencies": [
{ "name": "database", "healthy": true, "latency_ms": 12, "impact": null, "description": null, "contact": null, "type": "other" }
],
"warnings": ["No impact field mapping configured — impact data will not be captured"]
}On parse failure: { "success": false, "dependencies": [], "warnings": ["error message"] }.
External services are lightweight entries (name + description, no health endpoint) representing services outside Depsera's monitoring scope. They can be used as association targets for dependencies.
List external services. Non-admin users see only external services belonging to their teams. Admin users see all.
| Query Param | Type | Description |
|---|---|---|
team_id |
uuid | Optional. Filter by team |
curl http://localhost:3001/api/external-services -b cookies.txtResponse (200): Array of external service objects.
[
{
"id": "uuid",
"name": "Payment Gateway",
"team_id": "uuid",
"description": "Stripe integration",
"is_external": 1,
"team": { "id": "uuid", "name": "Platform" },
"created_at": "2024-01-15T10:00:00.000Z",
"updated_at": "2024-01-15T10:00:00.000Z"
}
]Create an external service. Requires team lead role on the specified team, or admin.
curl -X POST http://localhost:3001/api/external-services \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{ "name": "Payment Gateway", "team_id": "<team-uuid>", "description": "Stripe integration" }'Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | External service name |
team_id |
uuid | Yes | Owning team ID |
description |
string | No | Description of the external service |
Response (201): The created external service object.
Error responses:
| Status | Reason |
|---|---|
400 |
Validation error (missing name, invalid team) |
403 |
Not a team lead of the specified team |
Update an external service. Requires team lead role on the service's owning team, or admin.
curl -X PUT http://localhost:3001/api/external-services/<service-id> \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{ "name": "Updated Name", "description": "Updated description" }'Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | No | Updated name |
description |
string/null | No | Updated description (null to clear) |
Response (200): The updated external service object.
Error responses:
| Status | Reason |
|---|---|
400 |
Empty name or no changes provided |
404 |
External service not found |
Delete an external service. Cascades to associated dependency_associations. Requires team lead role on the service's owning team, or admin.
curl -X DELETE http://localhost:3001/api/external-services/<service-id> \
-H "X-CSRF-Token: <token>" \
-b cookies.txtResponse: 204 No Content
Error responses:
| Status | Reason |
|---|---|
404 |
External service not found |
List all services with their team and manifest key information. Used for discovering namespaced keys when authoring manifest associations. Requires authentication.
| Query Param | Type | Description |
|---|---|---|
search |
string | Optional. Filter by service name, manifest key, or team key |
team_id |
uuid | Optional. Filter by team |
curl http://localhost:3001/api/services/catalog -b cookies.txtResponse (200): Array of catalog entry objects.
[
{
"id": "uuid",
"name": "Payment Service",
"manifest_key": "payment-api",
"description": "Handles payment processing",
"is_active": 1,
"team_id": "uuid",
"team_name": "Platform",
"team_key": "platform"
}
]The team_key field enables constructing the namespaced team_key/manifest_key format used in manifest linked_service_key fields.
List all teams with member and service counts. Requires authentication.
curl http://localhost:3001/api/teams -b cookies.txtResponse (200): Array of team objects with counts.
[
{
"id": "uuid",
"name": "Platform",
"key": "platform",
"description": "Platform infrastructure team",
"member_count": 5,
"service_count": 3,
"created_at": "2024-01-10T08:00:00.000Z"
}
]Get team details with members and services. Requires authentication.
curl http://localhost:3001/api/teams/<team-id> -b cookies.txtResponse (200):
{
"id": "uuid",
"name": "Platform",
"key": "platform",
"description": "Platform infrastructure team",
"created_at": "2024-01-10T08:00:00.000Z",
"members": [
{
"user_id": "uuid",
"role": "lead",
"user": { "id": "uuid", "email": "lead@example.com", "name": "Team Lead", "role": "user" }
}
],
"services": [
{ "id": "uuid", "name": "Payment Service", "is_active": 1 }
]
}Create a new team. Requires admin role.
curl -X POST http://localhost:3001/api/teams \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{ "name": "Platform", "key": "platform", "description": "Platform infrastructure team" }'Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Team name (must be unique) |
key |
string | Yes | URL-friendly team identifier. Must match ^[a-z0-9][a-z0-9_-]*$, max 128 chars. Must be unique. |
description |
string | No | Team description |
Response (201): The created team object.
Error responses:
| Status | Reason |
|---|---|
400 |
Validation error — missing or invalid key |
409 |
Team name already exists |
Update a team. Requires admin role.
curl -X PUT http://localhost:3001/api/teams/<team-id> \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{ "description": "Updated description" }'Response (200): The updated team object.
Delete a team. Requires admin role. Fails if the team has services.
curl -X DELETE http://localhost:3001/api/teams/<team-id> \
-H "X-CSRF-Token: <token>" \
-b cookies.txtResponse: 204 No Content
Error responses:
| Status | Reason |
|---|---|
409 |
Team has services — delete or reassign them first |
Add a member to a team. Requires admin role.
curl -X POST http://localhost:3001/api/teams/<team-id>/members \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{ "user_id": "<user-uuid>", "role": "member" }'Request body:
| Field | Type | Required | Description |
|---|---|---|---|
user_id |
uuid | Yes | User to add |
role |
string | Yes | "lead" or "member" |
Response (201): The team membership object.
Error responses:
| Status | Reason |
|---|---|
409 |
User is already a member of this team |
Update a team member's role. Requires admin role.
curl -X PUT http://localhost:3001/api/teams/<team-id>/members/<user-id> \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{ "role": "lead" }'Response (200): The updated membership object.
Remove a member from a team. Requires admin role.
curl -X DELETE http://localhost:3001/api/teams/<team-id>/members/<user-id> \
-H "X-CSRF-Token: <token>" \
-b cookies.txtResponse: 204 No Content
All user management endpoints require admin role unless noted.
List all users.
curl http://localhost:3001/api/users -b cookies.txtResponse (200): Array of user objects.
[
{
"id": "uuid",
"email": "user@example.com",
"name": "User Name",
"role": "user",
"is_active": true,
"created_at": "2024-01-10T08:00:00.000Z"
}
]Get user details with team memberships.
curl http://localhost:3001/api/users/<user-id> -b cookies.txtResponse (200):
{
"id": "uuid",
"email": "user@example.com",
"name": "User Name",
"role": "user",
"is_active": true,
"created_at": "2024-01-10T08:00:00.000Z",
"teams": [
{ "team_id": "uuid", "role": "lead", "team_name": "Platform" }
]
}Create a local user. Only available when LOCAL_AUTH=true. Requires admin role.
curl -X POST http://localhost:3001/api/users \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{ "email": "new@example.com", "name": "New User", "password": "securepassword", "role": "user" }'Request body:
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | User email (must be unique) |
name |
string | Yes | Display name |
password |
string | Yes | Password (min 8 characters) |
role |
string | No | "admin" or "user" (default: "user") |
Error responses:
| Status | Reason |
|---|---|
400 |
Password too short or missing required fields |
404 |
Not in local auth mode |
409 |
Email already exists |
Update a user's role.
curl -X PUT http://localhost:3001/api/users/<user-id>/role \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{ "role": "admin" }'Request body:
| Field | Type | Required | Description |
|---|---|---|---|
role |
string | Yes | "admin" or "user" |
Error responses:
| Status | Reason |
|---|---|
400 |
Cannot demote the last admin |
Reset a user's password. Only available when LOCAL_AUTH=true. Requires admin role.
curl -X PUT http://localhost:3001/api/users/<user-id>/password \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{ "password": "newsecurepassword" }'Error responses:
| Status | Reason |
|---|---|
400 |
Password too short |
404 |
Not in local auth mode |
Deactivate a user (soft delete). Removes user from all team memberships.
curl -X DELETE http://localhost:3001/api/users/<user-id> \
-H "X-CSRF-Token: <token>" \
-b cookies.txtError responses:
| Status | Reason |
|---|---|
400 |
Cannot deactivate the last active admin |
Reactivate a previously deactivated user.
curl -X POST http://localhost:3001/api/users/<user-id>/reactivate \
-H "X-CSRF-Token: <token>" \
-b cookies.txtRead endpoints require authentication. Mutation endpoints (POST, PUT, DELETE) require admin role.
List all dependency aliases.
curl http://localhost:3001/api/aliases -b cookies.txtResponse (200):
[
{
"id": "uuid",
"alias": "pg-main",
"canonical_name": "PostgreSQL Primary",
"created_at": "2024-01-15T10:00:00.000Z"
}
]List distinct canonical names across all aliases.
curl http://localhost:3001/api/aliases/canonical-names -b cookies.txtResponse (200):
["PostgreSQL Primary", "Redis Cache", "RabbitMQ"]Create a new alias mapping. Requires admin role.
curl -X POST http://localhost:3001/api/aliases \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{ "alias": "pg-main", "canonical_name": "PostgreSQL Primary" }'Update an alias's canonical name. Requires admin role.
curl -X PUT http://localhost:3001/api/aliases/<alias-id> \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{ "canonical_name": "PostgreSQL Primary DB" }'Delete an alias. Requires admin role.
curl -X DELETE http://localhost:3001/api/aliases/<alias-id> \
-H "X-CSRF-Token: <token>" \
-b cookies.txtResponse: 204 No Content
Association mutations require team membership on the dependency's owning team.
Get associations for a dependency (non-dismissed only).
curl http://localhost:3001/api/dependencies/<dep-id>/associations -b cookies.txtCreate a manual association between a dependency and a service.
curl -X POST http://localhost:3001/api/dependencies/<dep-id>/associations \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{ "linked_service_id": "<service-uuid>", "association_type": "database" }'Request body:
| Field | Type | Required | Description |
|---|---|---|---|
linked_service_id |
uuid | Yes | Target service ID |
association_type |
string | Yes | Type of association (e.g., database, api, cache) |
Error responses:
| Status | Reason |
|---|---|
400 |
Cannot link a dependency to its own owning service |
409 |
Association already exists |
Remove an association.
curl -X DELETE http://localhost:3001/api/dependencies/<dep-id>/associations/<service-id> \
-H "X-CSRF-Token: <token>" \
-b cookies.txtResponse: 204 No Content
Get the dependency graph data for rendering. Returns nodes (services + external dependencies) and edges (dependency relationships).
| Query Param | Type | Description |
|---|---|---|
dependency |
uuid | Show subgraph for a specific dependency |
service |
uuid | Show subgraph for a specific service (with upstream traversal) |
team |
uuid | Show graph for a specific team's services |
Filters are evaluated in priority order: dependency > service > team. If no filters are provided, the full org-wide graph is returned.
# Full graph
curl http://localhost:3001/api/graph -b cookies.txt
# Team-filtered graph
curl "http://localhost:3001/api/graph?team=<team-id>" -b cookies.txtResponse (200):
{
"nodes": [
{
"id": "uuid",
"type": "service",
"data": {
"name": "Payment Service",
"teamId": "uuid",
"teamName": "Platform",
"healthEndpoint": "https://...",
"isActive": true,
"dependencyCount": 3,
"healthyCount": 2,
"unhealthyCount": 1,
"lastPollSuccess": true,
"lastPollError": null,
"serviceType": "rest",
"isExternal": false
}
}
],
"edges": [
{
"id": "sourceId-depId-type",
"source": "provider-service-id",
"target": "consumer-service-id",
"data": {
"relationship": "depends_on",
"dependencyType": "database",
"dependencyName": "postgres-main",
"dependencyId": "uuid",
"healthy": true,
"latencyMs": 12,
"avgLatencyMs24h": 15.3,
"associationType": "database",
"impact": "critical",
"errorMessage": null
}
}
]
}Get latency statistics and recent data points for a dependency (last 24 hours).
curl http://localhost:3001/api/latency/<dep-id> -b cookies.txtResponse (200):
{
"dependencyId": "uuid",
"currentLatencyMs": 12,
"avgLatencyMs24h": 15.3,
"minLatencyMs24h": 8,
"maxLatencyMs24h": 42,
"dataPointCount": 2880,
"dataPoints": [
{ "recorded_at": "2024-01-15T10:00:00.000Z", "latency_ms": 12 }
]
}Data points are limited to the last 100 records.
Get time-bucketed latency data for chart rendering.
| Query Param | Type | Description |
|---|---|---|
range |
string | Time range: 1h, 6h, 24h (default), 7d, 30d |
Bucket intervals: 1h/6h → 1-minute buckets, 24h → 15-minute, 7d → 1-hour, 30d → 6-hour.
curl "http://localhost:3001/api/latency/<dep-id>/buckets?range=7d" -b cookies.txtResponse (200):
{
"dependencyId": "uuid",
"range": "7d",
"buckets": [
{ "timestamp": "2024-01-15T10:00:00.000Z", "min": 8, "avg": 15, "max": 42, "count": 12 }
]
}Get error history for a dependency (last 24 hours, up to 50 records).
curl http://localhost:3001/api/errors/<dep-id> -b cookies.txtResponse (200):
{
"dependencyId": "uuid",
"errorCount": 5,
"errors": [
{
"error": { "code": "ECONNREFUSED" },
"errorMessage": "Connection refused",
"recordedAt": "2024-01-15T10:00:00.000Z",
"isRecovery": false
}
]
}isRecovery: true indicates a recovery event — error and errorMessage are null.
Get health state timeline showing transitions between healthy and unhealthy states.
| Query Param | Type | Description |
|---|---|---|
range |
string | Time range: 24h (default), 7d, 30d |
curl "http://localhost:3001/api/dependencies/<dep-id>/timeline?range=7d" -b cookies.txtResponse (200):
{
"dependencyId": "uuid",
"range": "7d",
"currentState": "healthy",
"transitions": [
{ "timestamp": "2024-01-15T09:00:00.000Z", "state": "unhealthy" },
{ "timestamp": "2024-01-15T09:05:00.000Z", "state": "healthy" }
]
}currentState is "healthy", "unhealthy", or "unknown" (when no health data exists).
All admin endpoints require admin role.
Query the audit log with optional filters.
| Query Param | Type | Description |
|---|---|---|
limit |
number | Max results (default: 50) |
offset |
number | Pagination offset |
startDate |
ISO date | Filter entries after this date |
endDate |
ISO date | Filter entries before this date |
userId |
uuid | Filter by acting user |
action |
string | Filter by action type |
resourceType |
string | Filter by resource type |
curl "http://localhost:3001/api/admin/audit-log?limit=20&action=user.role_changed" -b cookies.txtResponse (200):
{
"entries": [
{
"id": "uuid",
"user_id": "uuid",
"action": "user.role_changed",
"resource_type": "user",
"resource_id": "uuid",
"details": "{\"previousRole\":\"user\",\"newRole\":\"admin\"}",
"ip_address": "127.0.0.1",
"created_at": "2024-01-15T10:00:00.000Z",
"user_email": "admin@example.com",
"user_name": "Admin User"
}
],
"total": 42,
"limit": 20,
"offset": 0
}Audit action types: user.role_changed, user.deactivated, user.reactivated, team.created, team.updated, team.deleted, team.member_added, team.member_removed, team.member_role_changed, service.created, service.updated, service.deleted, external_service.created, external_service.updated, external_service.deleted, settings.updated, canonical_override.upserted, canonical_override.deleted, dependency_override.updated, dependency_override.cleared, manifest_sync, manifest_config.created, manifest_config.updated, manifest_config.deleted, drift.detected, drift.accepted, drift.dismissed, drift.reopened, drift.resolved, drift.bulk_accepted, drift.bulk_dismissed
Get all admin-configurable settings with current values and source.
curl http://localhost:3001/api/admin/settings -b cookies.txtResponse (200):
{
"data_retention_days": { "value": 365, "source": "default" },
"retention_cleanup_time": { "value": "02:00", "source": "default" },
"default_poll_interval_ms": { "value": 30000, "source": "default" },
"ssrf_allowlist": { "value": "localhost,127.0.0.0/8", "source": "database" },
"global_rate_limit": { "value": 3000, "source": "default" },
"global_rate_limit_window_minutes": { "value": 1, "source": "default" },
"auth_rate_limit": { "value": 20, "source": "default" },
"auth_rate_limit_window_minutes": { "value": 1, "source": "default" },
"alert_cooldown_minutes": { "value": 5, "source": "default" },
"alert_rate_limit_per_hour": { "value": 30, "source": "default" }
}source is "database" (admin-configured) or "default" (env var or built-in default).
Update admin settings. Accepts a partial object of key-value pairs.
curl -X PUT http://localhost:3001/api/admin/settings \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{ "data_retention_days": 180, "alert_cooldown_minutes": 10 }'Response (200): The full updated settings object (same shape as GET).
Team-scoped alert management. All endpoints are nested under /api/teams/:id.
List alert channels for a team. Requires team membership (any role).
curl http://localhost:3001/api/teams/<team-id>/alert-channels -b cookies.txtResponse (200):
[
{
"id": "uuid",
"team_id": "uuid",
"channel_type": "slack",
"config": { "webhook_url": "https://hooks.slack.com/services/T00/B00/xxx" },
"is_active": true,
"created_at": "2024-01-15T10:00:00.000Z"
}
]Create an alert channel. Requires team lead role or admin.
Slack channel:
curl -X POST http://localhost:3001/api/teams/<team-id>/alert-channels \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{
"channel_type": "slack",
"config": { "webhook_url": "https://hooks.slack.com/services/T00/B00/xxx" }
}'Webhook channel:
curl -X POST http://localhost:3001/api/teams/<team-id>/alert-channels \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{
"channel_type": "webhook",
"config": {
"url": "https://example.com/webhook",
"headers": { "Authorization": "Bearer token" },
"method": "POST"
}
}'Validation:
- Slack
webhook_urlmust matchhttps://hooks.slack.com/services/... - Webhook
urlmust be a valid URL - Webhook
headersvalues must be strings - Webhook
methodmust bePOST,PUT, orPATCH(default:POST)
Update an alert channel. Requires team lead role or admin.
curl -X PUT http://localhost:3001/api/teams/<team-id>/alert-channels/<channel-id> \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{ "is_active": false }'Delete an alert channel. Requires team lead role or admin.
curl -X DELETE http://localhost:3001/api/teams/<team-id>/alert-channels/<channel-id> \
-H "X-CSRF-Token: <token>" \
-b cookies.txtResponse: 204 No Content
Send a test alert to a channel. Requires team lead role or admin.
curl -X POST http://localhost:3001/api/teams/<team-id>/alert-channels/<channel-id>/test \
-H "X-CSRF-Token: <token>" \
-b cookies.txtResponse (200):
{ "success": true, "error": null }On failure: { "success": false, "error": "Connection timed out" }
Get alert rules for a team. Requires team membership (any role).
curl http://localhost:3001/api/teams/<team-id>/alert-rules -b cookies.txtResponse (200):
{
"id": "uuid",
"team_id": "uuid",
"severity_filter": "warning",
"is_active": true,
"created_at": "2024-01-15T10:00:00.000Z"
}Returns null if no rules are configured for the team.
Create or update (upsert) alert rules for a team. Requires team lead role or admin.
curl -X PUT http://localhost:3001/api/teams/<team-id>/alert-rules \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{ "severity_filter": "critical", "is_active": true }'Request body:
| Field | Type | Required | Description |
|---|---|---|---|
severity_filter |
string | Yes | "critical", "warning", or "all" |
is_active |
boolean | No | Enable/disable rules (default: true) |
Get alert delivery history for a team. Requires team membership (any role).
| Query Param | Type | Description |
|---|---|---|
limit |
number | Max results (default: 50) |
offset |
number | Pagination offset |
status |
string | Filter: sent, failed, suppressed |
curl "http://localhost:3001/api/teams/<team-id>/alert-history?status=failed&limit=10" -b cookies.txtResponse (200):
{
"entries": [
{
"id": "uuid",
"team_id": "uuid",
"channel_id": "uuid",
"channel_type": "slack",
"event_type": "status_change",
"payload": { "service": "Payment", "dependency": "postgres", "oldStatus": "healthy", "newStatus": "unhealthy" },
"status": "sent",
"error": null,
"created_at": "2024-01-15T10:00:00.000Z"
}
],
"total": 42,
"limit": 50,
"offset": 0
}Returns dependency-focused wallboard data. All dependencies across active services, deduplicated by canonical name, with aggregated health status and reporters. Admin sees all dependencies; non-admin users see only dependencies from their teams' services.
Auth: Session required
curl http://localhost:3001/api/wallboard \
-H "Cookie: deps-dashboard.sid=..."Response:
{
"dependencies": [
{
"canonical_name": "PostgreSQL",
"primary_dependency_id": "dep-abc123",
"health_status": "healthy",
"type": "database",
"latency": { "min": 10, "avg": 25, "max": 50 },
"last_checked": "2024-01-15T10:30:00.000Z",
"error_message": null,
"impact": null,
"description": "Primary database",
"effective_contact": "{\"email\":\"db-team@example.com\",\"slack\":\"#db-support\"}",
"effective_impact": "Critical — primary database",
"linked_service": { "id": "svc-xyz", "name": "Database Service" },
"reporters": [
{
"dependency_id": "dep-abc123",
"service_id": "svc-1",
"service_name": "User Service",
"service_team_id": "team-1",
"service_team_name": "Platform",
"healthy": 1,
"health_state": 0,
"latency_ms": 25,
"last_checked": "2024-01-15T10:30:00.000Z"
}
],
"team_ids": ["team-1"]
}
],
"teams": [
{ "id": "team-1", "name": "Platform" }
]
}Health status aggregation: When multiple services report the same dependency (matched by canonical name), the worst status wins: critical > warning > healthy > unknown.
Latency aggregation: min, avg, max computed across all reporters' current latency_ms values.
Primary dependency: The most recently checked dependency in the group, used for chart display (LatencyChart, HealthTimeline).
Override resolution: effective_contact and effective_impact are resolved from the primary dependency's 3-tier override hierarchy (instance > canonical > polled). Contact uses field-level merge; impact uses first-non-null precedence.
Canonical overrides set default contact and impact values for all dependencies sharing a canonical name. They sit in the middle of the 3-tier merge hierarchy: instance override > canonical override > polled data.
List all canonical overrides. Requires authentication.
curl http://localhost:3001/api/canonical-overrides -b cookies.txtResponse (200): Array of canonical override objects.
[
{
"id": "uuid",
"canonical_name": "PostgreSQL",
"contact_override": "{\"email\":\"db-team@example.com\",\"slack\":\"#db-support\"}",
"impact_override": "Critical — all downstream services depend on this",
"created_at": "2026-02-24T10:00:00.000Z",
"updated_at": "2026-02-24T10:00:00.000Z",
"updated_by": "user-uuid"
}
]Get a single canonical override by name. Requires authentication.
curl http://localhost:3001/api/canonical-overrides/PostgreSQL -b cookies.txtResponse (200): The canonical override object. Returns 404 if not found.
Create or update a canonical override. Requires admin role OR team lead of any team that owns a service with a dependency matching the given canonical name.
curl -X PUT http://localhost:3001/api/canonical-overrides/PostgreSQL \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{
"contact_override": { "email": "db-team@example.com", "slack": "#db-support" },
"impact_override": "Critical — all downstream services depend on this"
}'Request body:
| Field | Type | Required | Description |
|---|---|---|---|
contact_override |
object/null | No | Contact info object (set to null to clear). Stored as JSON string. |
impact_override |
string/null | No | Impact description (set to null to clear). |
At least one field must be provided (400 otherwise).
Response (200): The created or updated canonical override object.
Audit action: canonical_override.upserted
Delete a canonical override. Same permission requirements as PUT.
curl -X DELETE http://localhost:3001/api/canonical-overrides/PostgreSQL \
-H "X-CSRF-Token: <token>" \
-b cookies.txtResponse: 204 No Content
Audit action: canonical_override.deleted
Per-instance overrides set contact and/or impact for a specific dependency instance (service-dependency pair). These take highest precedence in the merge hierarchy: instance override > canonical override > polled data.
Set per-instance overrides on a dependency. Requires admin role OR team lead of the team that owns the service reporting this dependency.
curl -X PUT http://localhost:3001/api/dependencies/<dep-id>/overrides \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{
"contact_override": { "email": "db-team@example.com", "slack": "#db-support" },
"impact_override": "Critical — primary database"
}'Request body:
| Field | Type | Required | Description |
|---|---|---|---|
contact_override |
object/null | No | Contact info object (set to null to clear). Stored as JSON string. |
impact_override |
string/null | No | Impact description (set to null to clear). |
At least one field must be provided (400 otherwise).
Response (200): The full updated dependency row.
Audit action: dependency_override.updated
Clear all per-instance overrides on a dependency (sets both contact_override and impact_override to null). Does not modify polled data columns. Same permission requirements as PUT.
curl -X DELETE http://localhost:3001/api/dependencies/<dep-id>/overrides \
-H "X-CSRF-Token: <token>" \
-b cookies.txtResponse: 204 No Content
Audit action: dependency_override.cleared
Team-scoped manifest management. Manifests define services, aliases, overrides, and associations declaratively via a JSON URL. See the Manifest Schema Reference for the full schema, validation rules, and examples.
Get the manifest configuration for a team. Returns null if no manifest is configured.
Auth: Team member (any role)
curl http://localhost:3001/api/teams/<team-id>/manifest -b cookies.txtResponse (200):
{
"id": "uuid",
"team_id": "uuid",
"manifest_url": "https://example.com/manifest.json",
"is_enabled": 1,
"sync_policy": "{\"on_field_drift\":\"flag\",\"on_removal\":\"flag\",\"on_alias_removal\":\"keep\",\"on_override_removal\":\"keep\",\"on_association_removal\":\"keep\"}",
"last_sync_at": "2026-02-28T10:00:00.000Z",
"last_sync_status": "success",
"last_sync_error": null,
"last_sync_summary": "{\"services\":{\"created\":2,\"updated\":0,\"deactivated\":0,\"deleted\":0,\"drift_flagged\":0,\"unchanged\":1},\"aliases\":{\"created\":1,\"updated\":0,\"removed\":0,\"unchanged\":0},\"overrides\":{\"created\":0,\"updated\":0,\"removed\":0,\"unchanged\":0},\"associations\":{\"created\":1,\"removed\":0,\"unchanged\":0}}",
"created_at": "2026-02-28T09:00:00.000Z",
"updated_at": "2026-02-28T10:00:00.000Z"
}Returns null body when no manifest is configured for the team.
Create or update a team's manifest configuration. Requires team lead or admin role.
curl -X PUT http://localhost:3001/api/teams/<team-id>/manifest \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{
"manifest_url": "https://example.com/manifest.json",
"sync_policy": {
"on_field_drift": "flag",
"on_removal": "deactivate"
}
}'Request body:
| Field | Type | Required | Description |
|---|---|---|---|
manifest_url |
string | Yes | HTTP or HTTPS URL pointing to a JSON manifest. SSRF-validated. |
is_enabled |
boolean | No | Enable/disable scheduled sync (default: true). |
sync_policy |
object | No | Partial sync policy. Unset fields use defaults. |
Sync policy fields:
| Field | Values | Default | Description |
|---|---|---|---|
on_field_drift |
flag, manifest_wins, local_wins |
flag |
How to handle manually edited fields. |
on_removal |
flag, deactivate, delete |
flag |
How to handle services removed from manifest. |
on_alias_removal |
remove, keep |
keep |
How to handle aliases removed from manifest. |
on_override_removal |
remove, keep |
keep |
How to handle overrides removed from manifest. |
on_association_removal |
remove, keep |
keep |
How to handle associations removed from manifest. |
Response (200): The created or updated manifest config object.
Errors:
| Status | Condition |
|---|---|
400 |
Invalid URL format, SSRF-blocked hostname, or invalid sync policy values |
403 |
Not a team lead or admin |
Audit actions: manifest_config.created or manifest_config.updated
Remove a team's manifest configuration. Does not delete services that were created by the manifest — they remain active but are no longer manifest-managed.
Auth: Team lead or admin
curl -X DELETE http://localhost:3001/api/teams/<team-id>/manifest \
-H "X-CSRF-Token: <token>" \
-b cookies.txtResponse: 204 No Content
Audit action: manifest_config.deleted
Trigger a manual sync for a team's manifest. Fetches the manifest URL, validates, diffs against current state, and applies changes.
Auth: Team member (any role)
curl -X POST http://localhost:3001/api/teams/<team-id>/manifest/sync \
-H "X-CSRF-Token: <token>" \
-b cookies.txtResponse (200):
{
"status": "success",
"summary": {
"services": {
"created": 2,
"updated": 1,
"deactivated": 0,
"deleted": 0,
"drift_flagged": 1,
"unchanged": 3
},
"aliases": {
"created": 1,
"updated": 0,
"removed": 0,
"unchanged": 2
},
"overrides": {
"created": 0,
"updated": 0,
"removed": 0,
"unchanged": 1
},
"associations": {
"created": 1,
"removed": 0,
"unchanged": 2
}
},
"errors": [],
"warnings": ["services[2].health_endpoint: URL targets a private or internal address"],
"changes": [
{ "manifest_key": "svc-a", "service_name": "Service A", "action": "created" },
{ "manifest_key": "svc-b", "service_name": "Service B", "action": "updated", "fields_changed": ["description"] },
{ "manifest_key": "svc-c", "service_name": "Service C", "action": "drift_flagged", "drift_fields": ["health_endpoint"] }
],
"duration_ms": 1250
}Status values:
| Status | Meaning |
|---|---|
success |
All entries processed without errors |
partial |
Some entries succeeded, others had errors (e.g., SSRF-blocked endpoints) |
failed |
Sync failed entirely (fetch error, validation failure, etc.) |
Errors:
| Status | Condition |
|---|---|
404 |
No manifest configured for this team |
400 |
Manifest is disabled |
409 |
Sync already in progress for this team |
429 |
Manual sync cooldown active (60s). Includes Retry-After header. |
Audit action: manifest_sync
Get paginated sync history for a team.
Auth: Team member (any role)
| Query Param | Type | Default | Description |
|---|---|---|---|
limit |
number | 20 | Max results (max 100) |
offset |
number | 0 | Pagination offset |
curl "http://localhost:3001/api/teams/<team-id>/manifest/sync-history?limit=10" -b cookies.txtResponse (200):
{
"entries": [
{
"id": "uuid",
"team_id": "uuid",
"trigger_type": "manual",
"triggered_by": "user-uuid",
"manifest_url": "https://example.com/manifest.json",
"status": "success",
"summary": "{\"services\":{\"created\":2,\"updated\":0,...}}",
"errors": null,
"warnings": null,
"duration_ms": 850,
"created_at": "2026-02-28T10:00:00.000Z"
}
],
"total": 15
}Dry-run manifest validation. Validates the provided JSON against the manifest schema without persisting anything or triggering a sync.
Auth: Any authenticated user
curl -X POST http://localhost:3001/api/manifest/validate \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{
"version": 1,
"services": [
{
"key": "test-svc",
"name": "Test Service",
"health_endpoint": "https://test.example.com/health"
}
]
}'Response (200):
{
"valid": true,
"version": 1,
"service_count": 1,
"valid_count": 1,
"errors": [],
"warnings": []
}Drift flags are created when the sync engine detects differences between the manifest and local state. Flags require review and action (accept, dismiss, or reopen).
List drift flags for a team with filtering. Defaults to pending flags.
Auth: Team member (any role)
| Query Param | Type | Default | Description |
|---|---|---|---|
status |
string | pending |
Filter: pending, dismissed, accepted, resolved |
drift_type |
string | — | Filter: field_change, service_removal |
service_id |
uuid | — | Filter by service |
limit |
number | 50 | Max results (max 250) |
offset |
number | 0 | Pagination offset |
curl "http://localhost:3001/api/teams/<team-id>/drifts?status=pending&drift_type=field_change" -b cookies.txtResponse (200):
{
"flags": [
{
"id": "uuid",
"team_id": "uuid",
"service_id": "uuid",
"service_name": "Payment API",
"manifest_key": "payment-api",
"drift_type": "field_change",
"field_name": "health_endpoint",
"manifest_value": "https://payment-v2.example.com/health",
"current_value": "https://payment.example.com/health",
"status": "pending",
"first_detected_at": "2026-02-28T10:00:00.000Z",
"last_detected_at": "2026-02-28T11:00:00.000Z",
"resolved_at": null,
"resolved_by": null,
"sync_history_id": "uuid",
"created_at": "2026-02-28T10:00:00.000Z"
}
],
"summary": {
"pending_count": 3,
"dismissed_count": 1,
"field_change_pending": 2,
"service_removal_pending": 1
},
"total": 3
}Get lightweight drift flag counts for badge display.
Auth: Team member (any role)
curl http://localhost:3001/api/teams/<team-id>/drifts/summary -b cookies.txtResponse (200):
{
"pending_count": 3,
"dismissed_count": 1,
"field_change_pending": 2,
"service_removal_pending": 1
}Accept a drift flag. Applies the manifest value to the service.
Auth: Team lead or admin
curl -X PUT http://localhost:3001/api/teams/<team-id>/drifts/<drift-id>/accept \
-H "X-CSRF-Token: <token>" \
-b cookies.txtAccept behavior by drift type:
field_change: Updates the service field tomanifest_value. Re-validates SSRF for URL fields. Validatespoll_interval_msbounds. Updates the synced snapshot. Restarts polling ifhealth_endpointorpoll_interval_mschanged.service_removal: Deactivates the service (is_active=0) and stops polling.
Response (200): The updated drift flag object.
Errors:
| Status | Condition |
|---|---|
400 |
SSRF validation failed for a URL field, or invalid bounds |
404 |
Drift flag not found or belongs to a different team |
409 |
Flag already accepted or resolved |
Audit action: drift.accepted
Dismiss a drift flag. The flag remains visible in the dismissed view.
Auth: Team lead or admin
curl -X PUT http://localhost:3001/api/teams/<team-id>/drifts/<drift-id>/dismiss \
-H "X-CSRF-Token: <token>" \
-b cookies.txtResponse (200): The updated drift flag object.
Audit action: drift.dismissed
Reopen a previously dismissed drift flag back to pending.
Auth: Team lead or admin
curl -X PUT http://localhost:3001/api/teams/<team-id>/drifts/<drift-id>/reopen \
-H "X-CSRF-Token: <token>" \
-b cookies.txtResponse (200): The updated drift flag object with status: "pending".
Errors: Returns 400 if the flag is not in dismissed status.
Audit action: drift.reopened
Bulk accept drift flags. Processes up to 100 flags in a transaction. Best-effort: SSRF failures on individual URL fields skip that flag but continue with others.
Auth: Team lead or admin
curl -X POST http://localhost:3001/api/teams/<team-id>/drifts/bulk-accept \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{ "flag_ids": ["drift-id-1", "drift-id-2"] }'Request body:
| Field | Type | Required | Description |
|---|---|---|---|
flag_ids |
string[] | Yes | Array of drift flag IDs to accept. Max 100. |
Response (200):
{
"result": {
"succeeded": 2,
"failed": 0,
"errors": []
}
}Audit action: drift.bulk_accepted
Bulk dismiss drift flags. Processes up to 100 flags in a transaction.
Auth: Team lead or admin
curl -X POST http://localhost:3001/api/teams/<team-id>/drifts/bulk-dismiss \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: <token>" \
-b cookies.txt \
-d '{ "flag_ids": ["drift-id-1", "drift-id-2"] }'Request body: Same as bulk-accept.
Response (200): Same shape as bulk-accept.
Audit action: drift.bulk_dismissed