{"openapi":"3.1.0","info":{"title":"CuliPulse API","version":"1.0.0"},"servers":[{"url":"https://culipulse.dev/v1","description":"Production"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API token with prefix cpk_. Scopes: read, write."}},"schemas":{"CheckSpec":{"type":"object","properties":{"request":{"type":"object","description":"HTTP monitors: what to send.","properties":{"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Plain request headers."},"secretHeaders":{"type":"object","additionalProperties":{"type":"string"},"description":"Write-only. Headers stored encrypted; reads return their names in `secretHeaderNames`. Send {} to clear."},"preserveSecretHeaders":{"type":"boolean","description":"Write-only. true keeps the stored secret headers."},"secretHeaderNames":{"type":"array","items":{"type":"string"},"description":"Read-only. Names of the stored secret headers."},"auth":{"type":"object","description":"Reads return `type` only. Send `type` alone to keep the stored credentials.","properties":{"type":{"type":"string","enum":["bearer","basic"]},"token":{"type":"string"},"username":{"type":"string"},"password":{"type":"string"}}},"body":{"type":"string","description":"Request body."},"bodySecret":{"type":"boolean","description":"On write, stores `body` encrypted. On read, true means a secret body is set (it isn't returned)."},"preserveBody":{"type":"boolean","description":"Write-only, with bodySecret: true keeps the stored secret body."},"contentType":{"type":"string"}}},"assertions":{"type":"array","description":"HTTP monitors: extra checks on the response.","items":{"type":"object","properties":{"source":{"type":"string","enum":["text_body","json_body","header","response_time","cert_expiry"]},"op":{"type":"string"},"value":{"type":"string"},"path":{"type":"string","description":"json_body only."},"name":{"type":"string","description":"header only."}}}},"transport":{"type":"object","description":"UDP monitors: what to send and expect.","properties":{"payload":{"type":"string"},"payloadEncoding":{"type":"string","enum":["utf8","hex"]},"expect":{"type":"string"},"expectMode":{"type":"string","enum":["contains","regex"]},"payloadSecret":{"type":"boolean","description":"On write, stores `payload` encrypted. On read, true means a secret payload is set."},"preservePayload":{"type":"boolean","description":"Write-only, with payloadSecret: true keeps the stored secret payload."}}},"domain":{"type":"object","description":"Days of warning before a domain or certificate expires.","properties":{"warnDays":{"type":"integer"}}},"ping":{"type":"object","description":"ICMP monitors.","properties":{"count":{"type":"integer"},"lossDegradedPct":{"type":"integer"},"rttDegradedMs":{"type":"integer"}}},"heartbeat":{"type":"object","description":"Heartbeat monitors. The token is created by the server and kept on update.","properties":{"token":{"type":"string"},"failEnabled":{"type":"boolean"},"maxDurationSeconds":{"type":"integer"}}},"dns":{"type":"object","description":"DNS monitors.","properties":{"records":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"mode":{"type":"string"},"expected":{"type":"array","items":{"type":"string"}}}}},"crossCheck":{"type":"boolean"}}},"vendor":{"type":"object","description":"Read-only. Vendor status monitors: set by the server from `target`.","properties":{"vendorId":{"type":"string"},"pageUrl":{"type":"string"},"probeUrl":{"type":"string"},"components":{"type":"array","items":{"type":"string"}}}},"dependencies":{"type":"array","description":"Read-only. Ids of the vendor monitors this monitor depends on.","items":{"type":"string"}}},"description":"Returned by GET /monitors/{id}; omitted from the list."},"Monitor":{"type":"object","properties":{"id":{"type":"string","example":"mon_123"},"name":{"type":"string"},"type":{"type":"string","enum":["http","tcp","icmp","udp","domain","heartbeat","dns","vendor"]},"target":{"type":"string"},"interval_seconds":{"type":"integer"},"timeout_ms":{"type":"integer"},"method":{"type":["string","null"]},"expected_status":{"type":["string","null"]},"body_match":{"type":["string","null"]},"follow_redirects":{"type":"integer","description":"1 = follow redirects, 0 = don't"},"down_after_failures":{"type":"integer"},"up_after_successes":{"type":"integer"},"down_min_sources":{"type":"integer","description":"How many agents must see a failure before the monitor is marked down. Only used by monitors that check from agents (http, tcp, icmp, udp); vendor, domain and dns are checked once by CuliPulse, so it is always 1 for them."},"heartbeat_grace_seconds":{"type":["integer","null"],"description":"Heartbeat only. Written as `grace_seconds`."},"sla_target":{"type":["number","null"]},"status":{"type":"string","enum":["up","down","degraded","paused","pending"]},"enabled":{"type":"integer"},"created_at":{"type":"integer","description":"Unix seconds"},"updated_at":{"type":"integer","description":"Unix seconds"},"check_spec":{"$ref":"#/components/schemas/CheckSpec"},"agent_sources":{"type":"array","items":{"type":"string"},"description":"Agent ids probing this monitor. Returned by GET /monitors/{id}."}},"required":["id","name","type","target","interval_seconds","timeout_ms","method","expected_status","body_match","follow_redirects","down_after_failures","up_after_successes","down_min_sources","heartbeat_grace_seconds","sla_target","status","enabled","created_at","updated_at"]},"MonitorList":{"type":"object","properties":{"monitors":{"type":"array","items":{"$ref":"#/components/schemas/Monitor"}}},"required":["monitors"]},"Error":{"type":"object","properties":{"error":{"type":"string","example":"not found"},"request_id":{"type":"string","example":"req_abc123"}},"required":["error"]},"Overview":{"type":"object","properties":{"total":{"type":"number"},"up":{"type":"number"},"down":{"type":"number"},"monitors":{"type":"array","items":{"$ref":"#/components/schemas/Monitor"}}},"required":["total","up","down","monitors"]},"MonitorMutationResult":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"}},"required":["id","status"]},"MonitorCreate":{"type":"object","properties":{"name":{"type":"string","example":"API health"},"type":{"type":"string","description":"One of http, tcp, icmp, udp, domain, heartbeat, dns, vendor. Defaults to http. Cannot be changed later.","example":"http"},"target":{"type":"string","description":"Not used by heartbeat monitors.","example":"https://api.example.com/health"},"interval_seconds":{"type":"number","example":60},"timeout_ms":{"type":"number","description":"Max 10000 and less than interval_seconds × 1000."},"method":{"type":"string","description":"Case-insensitive. One of GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS; anything else is a 400."},"expected_status":{"type":"string","description":"Codes like '200', '2xx' or '200,301'.","example":"2xx"},"body_match":{"type":["string","null"],"description":"Send an empty string or null to clear."},"follow_redirects":{"anyOf":[{"type":"boolean"},{"type":"number"}]},"down_after_failures":{"type":"number","description":"Integer 1..5."},"up_after_successes":{"type":"number","description":"Integer 1..5."},"down_min_sources":{"type":["number","null"],"description":"Integer 1..number of agents. Only applies to monitors that check from agents; for vendor, domain and dns only 1 is accepted."},"sla_target":{"type":["number","null"],"description":"Percent in (0, 100]. null clears it."},"grace_seconds":{"type":"number","description":"Heartbeat only."},"agent_sources":{"type":"array","items":{"type":"string"},"description":"Agent ids to probe from. Omit on PATCH to keep the current set."},"check_spec":{"allOf":[{"$ref":"#/components/schemas/CheckSpec"},{"description":"Type-specific settings. Only the keys for the monitor's type apply. Secret values are never returned: reads show `request.secretHeaderNames` and `request.auth.type` only."}]},"warn_days":{"type":"integer","description":"http: days of warning before the site's domain expires; 1 or more turns the check on, 0 turns it off. domain: the warning threshold, 1..3650 (default 30). Omit on PATCH to keep the current value."},"components":{"type":"array","description":"Vendor monitors only: the status-page components to watch. Statuspage vendors: omit or send [] to watch the whole vendor. AWS: `service:region` targets, at least one. Omit on PATCH to keep the current list.","items":{"type":"string"}},"dependencies":{"type":"array","description":"Ids of your vendor monitors this monitor depends on (up to 50); ids that aren't one of your vendor monitors are ignored. Not allowed on vendor monitors. Omit on PATCH to keep the current list; send [] to clear it.","items":{"type":"string"}}},"required":["name"]},"MonitorPatch":{"type":"object","properties":{"name":{"type":"string"},"target":{"type":"string","description":"Not used by heartbeat monitors.","example":"https://api.example.com/health"},"interval_seconds":{"type":"number","example":60},"timeout_ms":{"type":"number","description":"Max 10000 and less than interval_seconds × 1000."},"method":{"type":"string","description":"Case-insensitive. One of GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS; anything else is a 400."},"expected_status":{"type":"string","description":"Codes like '200', '2xx' or '200,301'.","example":"2xx"},"body_match":{"type":["string","null"],"description":"Send an empty string or null to clear."},"follow_redirects":{"anyOf":[{"type":"boolean"},{"type":"number"}]},"down_after_failures":{"type":"number","description":"Integer 1..5."},"up_after_successes":{"type":"number","description":"Integer 1..5."},"down_min_sources":{"type":["number","null"],"description":"Omitting it (or sending null) resets it to the default quorum. Only applies to monitors that check from agents; for vendor, domain and dns only 1 is accepted."},"sla_target":{"type":["number","null"],"description":"Percent in (0, 100]. null clears it."},"grace_seconds":{"type":"number","description":"Heartbeat only."},"agent_sources":{"type":"array","items":{"type":"string"},"description":"Agent ids to probe from. Omit on PATCH to keep the current set."},"check_spec":{"allOf":[{"$ref":"#/components/schemas/CheckSpec"},{"description":"Replaces the whole object. Omitting it clears headers, secret headers, auth, body and assertions on http monitors — always send the full check_spec. Heartbeat monitors keep their settings (such as `heartbeat.maxDurationSeconds`) when it's omitted; sending it replaces them."}]},"warn_days":{"type":"integer","description":"http: days of warning before the site's domain expires; 1 or more turns the check on, 0 turns it off. domain: the warning threshold, 1..3650 (default 30). Omit on PATCH to keep the current value."},"components":{"type":"array","description":"Vendor monitors only: the status-page components to watch. Statuspage vendors: omit or send [] to watch the whole vendor. AWS: `service:region` targets, at least one. Omit on PATCH to keep the current list.","items":{"type":"string"}},"dependencies":{"type":"array","description":"Ids of your vendor monitors this monitor depends on (up to 50); ids that aren't one of your vendor monitors are ignored. Not allowed on vendor monitors. Omit on PATCH to keep the current list; send [] to clear it.","items":{"type":"string"}}},"description":"Partial update: omitted fields keep their current value, EXCEPT `check_spec` and `down_min_sources` — see their own field descriptions. Sending a `type` different from the current one returns 400."},"Incident":{"type":"object","properties":{}},"IncidentList":{"type":"object","properties":{"incidents":{"type":"array","items":{"$ref":"#/components/schemas/Incident"}}},"required":["incidents"]},"Channel":{"type":"object","properties":{"id":{"type":"string","example":"ch_123"},"type":{"type":"string","enum":["telegram","slack","webhook"]},"name":{"type":"string"},"enabled":{"type":"boolean"},"created_at":{"type":"integer"},"status":{"type":"string","enum":["connected","pending"]},"all_monitors":{"type":"boolean","description":"true = alerts for every monitor; false = only `monitor_ids`."},"monitor_ids":{"type":"array","items":{"type":"string"},"description":"Monitors routed to this channel when all_monitors is false. Sorted."},"url_host":{"type":["string","null"],"description":"Webhook only. The full URL is never returned."},"team_name":{"type":["string","null"],"description":"Slack only."},"channel_name":{"type":["string","null"],"description":"Slack only."}},"required":["id","type","name","enabled","created_at","status","all_monitors","monitor_ids"]},"ChannelList":{"type":"object","properties":{"channels":{"type":"array","items":{"$ref":"#/components/schemas/Channel"}}},"required":["channels"]},"ChannelCreateResult":{"type":"object","properties":{"id":{"type":"string"},"signing_secret":{"type":"string","description":"Webhook only. Returned once, at create. Store it now."},"deep_link":{"type":"string","description":"Telegram only. Open it to finish connecting."},"group_deep_link":{"type":"string","description":"Telegram only."},"authorize_url":{"type":"string","description":"Slack only. Open it to finish connecting."}},"required":["id"]},"ChannelCreate":{"type":"object","properties":{"type":{"type":"string","description":"telegram (default), slack or webhook.","example":"webhook"},"name":{"type":"string"},"url":{"type":"string","description":"Webhook only. Must be https. A test delivery is sent before the channel is saved."},"all_monitors":{"type":"boolean","description":"Defaults to true."},"monitor_ids":{"type":"array","items":{"type":"string"}}}},"OkResult":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}},"required":["ok"]},"ChannelPatch":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"all_monitors":{"type":"boolean"},"monitor_ids":{"type":"array","items":{"type":"string"},"description":"Replaces the whole routing list."}},"description":"Changing `type` or sending `url` returns 400. Delete and recreate the channel instead."},"Agent":{"type":"object","properties":{"id":{"type":"string"},"tenant_id":{"type":["string","null"],"description":"null for CuliPulse-run (first_party) agents."},"kind":{"type":"string","enum":["tenant","first_party"]},"name":{"type":"string"},"region":{"type":["string","null"]},"capabilities":{},"version":{"type":["string","null"]},"arch":{"type":["string","null"]},"last_seen_at":{"type":["integer","null"]},"created_at":{"type":"integer"}},"required":["id","tenant_id","kind","name","region","version","arch","last_seen_at","created_at"]},"AgentList":{"type":"object","properties":{"agents":{"type":"array","items":{"$ref":"#/components/schemas/Agent"}}},"required":["agents"]},"Report":{"type":"object","properties":{}},"ReportList":{"type":"object","properties":{"reports":{"type":"array","items":{"$ref":"#/components/schemas/Report"}}},"required":["reports"]}},"parameters":{}},"paths":{"/monitors":{"get":{"tags":["Monitors"],"security":[{"bearerAuth":["read"]}],"responses":{"200":{"description":"List monitors","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MonitorList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Monitors"],"security":[{"bearerAuth":["write"]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MonitorCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MonitorMutationResult"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/overview":{"get":{"tags":["Monitors"],"security":[{"bearerAuth":["read"]}],"responses":{"200":{"description":"Tenant monitor overview","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Overview"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/monitors/{id}":{"get":{"tags":["Monitors"],"security":[{"bearerAuth":["read"]}],"parameters":[{"schema":{"type":"string","example":"mon_123"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Monitor detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Monitor"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Monitors"],"security":[{"bearerAuth":["write"]}],"parameters":[{"schema":{"type":"string","example":"mon_123"},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MonitorPatch"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MonitorMutationResult"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Monitors"],"security":[{"bearerAuth":["write"]}],"parameters":[{"schema":{"type":"string","example":"mon_123"},"required":true,"name":"id","in":"path"}],"responses":{"204":{"description":"Deleted"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/monitors/{id}/incidents":{"get":{"tags":["Incidents"],"security":[{"bearerAuth":["read"]}],"parameters":[{"schema":{"type":"string","example":"mon_123"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Incidents for a monitor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IncidentList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/incidents":{"get":{"tags":["Incidents"],"security":[{"bearerAuth":["read"]}],"responses":{"200":{"description":"Incidents for the tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IncidentList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/channels":{"get":{"tags":["Channels"],"security":[{"bearerAuth":["read"]}],"responses":{"200":{"description":"List channels","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Channels"],"security":[{"bearerAuth":["write"]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelCreateResult"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/channels/{id}":{"get":{"tags":["Channels"],"security":[{"bearerAuth":["read"]}],"parameters":[{"schema":{"type":"string","example":"mon_123"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Channel detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Channel"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Channels"],"security":[{"bearerAuth":["write"]}],"parameters":[{"schema":{"type":"string","example":"mon_123"},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelPatch"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OkResult"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Channels"],"security":[{"bearerAuth":["write"]}],"parameters":[{"schema":{"type":"string","example":"mon_123"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OkResult"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/agents":{"get":{"tags":["Agents"],"security":[{"bearerAuth":["read"]}],"responses":{"200":{"description":"List agents","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/agents/{id}":{"get":{"tags":["Agents"],"security":[{"bearerAuth":["read"]}],"parameters":[{"schema":{"type":"string","example":"mon_123"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Agent detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Agent"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Agents"],"security":[{"bearerAuth":["write"]}],"parameters":[{"schema":{"type":"string","example":"mon_123"},"required":true,"name":"id","in":"path"}],"responses":{"204":{"description":"Deleted"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/reports":{"get":{"tags":["Reports"],"security":[{"bearerAuth":["read"]}],"responses":{"200":{"description":"List reports","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/reports/{id}":{"get":{"tags":["Reports"],"security":[{"bearerAuth":["read"]}],"parameters":[{"schema":{"type":"string","example":"mon_123"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Report detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Report"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"webhooks":{}}