REST API
Manage monitors, rules, incidents and callback endpoints over HTTPS. To send beats, see Publish a heartbeat. Every operation is listed in the API reference.
Get started
Section titled “Get started”- Origin:
https://api.signalsitter.com - Base path:
/v1 - Auth:
Authorization: Bearer <your API credential> - Contract: the OpenAPI document, also served at
GET /v1/openapi.json
- Open the dashboard, pick a project, and
create an API credential. Use
readfor read-only work. Managing callback endpoints needsmanage. - Copy the secret into your secret store. It’s shown once.
- Load it into
SIGNALSITTER_API_KEYand set the origin:
export SIGNALSITTER_API_BASE=https://api.signalsitter.comList your monitors:
curl --fail-with-body \ --header "Authorization: Bearer $SIGNALSITTER_API_KEY" \ "$SIGNALSITTER_API_BASE/v1/monitors"Create a monitor that expects a beat every day, with ten minutes of grace:
curl --fail-with-body \ --request POST \ --header "Authorization: Bearer $SIGNALSITTER_API_KEY" \ --header "Content-Type: application/json" \ --data '{"name":"Nightly backup","intervalSeconds":86400,"graceSeconds":600,"callback":{"kind":"organization_default"}}' \ "$SIGNALSITTER_API_BASE/v1/monitors"The response carries publishUrl, the address to send beats to, and
monitorKey, the monitor’s ID. Use that ID as {monitorId} in every other
monitor route.
Projects
Section titled “Projects”An API credential belongs to the project it was created in. Every request made with it acts in that project, so there is nothing to send.
- To work in another project, create a credential in that project.
- A
Signalsitter-Projectheader naming a different project is refused withsignalsitter.not_found. - Signed-in dashboard sessions name their project in
Signalsitter-Project.
Listing projects with an API credential returns only that credential’s project.
Project names are unique in an organization, ignoring case and extra spaces, so
creating a duplicate returns signalsitter.conflict. See
List projects,
Create project and
Rename project.
Common tasks
Section titled “Common tasks”Create and configure a monitor
Section titled “Create and configure a monitor”Only name is required at creation. Also accepted:
intervalSeconds: 60 to 31,536,000. Leave it out and the monitor measures its own cadence from the beats it receives.graceSeconds: lateness tolerated after the interval. Values below 60 are raised to 60.jurisdiction:unrestricted,eu,usorfedramp. It can’t change later.callback: where alerts go. See callback bindings.
Then:
- Watch for the first beat with
Wait for first beat. It returns
waiting, thenacceptedwith the first acceptance time and next deadline. - Check a sample beat without recording anything with Test a beat.
To change a monitor, read it first. The response’s ETag header carries its
configuration version, such as "configuration-3". Send it back in If-Match:
curl --fail-with-body \ --request PATCH \ --header "Authorization: Bearer $SIGNALSITTER_API_KEY" \ --header "Content-Type: application/json" \ --header 'If-Match: "configuration-3"' \ --data '{"patch":{"graceSeconds":900}}' \ "$SIGNALSITTER_API_BASE/v1/monitors/nightly-backup"Fields you leave out of patch keep their current values. The callback binding
has its own route, below.
Change lifecycle takes one action
and the same If-Match:
pause: beats are still recorded, but no deadlines run and no incidents open.resume: the clock restarts with a full interval plus grace.archive: further beats are refused withsignalsitter.monitor_archived. History is kept, and the monitor reportsrestorableUntil.restore: counts against your plan’s active-monitor allowance. At the limit, free a slot or upgrade first.enter_maintenance: needs awindowwithstartsAtandendsAt, at most 30 days. Missed deadlines inside it don’t open incidents. The next deadline moves to the window’s end.exit_maintenance: ends the window early and restarts the clock.
Delete monitor is permanent. It needs
If-Match, closes open incidents and cancels pending callbacks.
Read a monitor and its history
Section titled “Read a monitor and its history”| Operation | Returns |
|---|---|
| List monitors | Every monitor with live status: lifecycle, last beat, next deadline, open incident |
| Get monitor | Configuration, latest observation and the ETag |
| List observations | Accepted beats, newest first |
| Read series | Graph-ready minimum, maximum and mean per time bucket |
| List changes | Configuration and lifecycle changes, with who made each one |
Observations take optional since and until timestamps (RFC 3339). The
series needs both, up to 365 days apart. On a paid plan, add
includeObservationBuckets=true to read summarized history beyond 30 days.
If the same monitor ID exists in two jurisdictions, a request returns
signalsitter.conflict. Retry with ?jurisdiction=eu (or the one you mean).
Set limits with the rule workbench
Section titled “Set limits with the rule workbench”Set a numeric rule directly with a configuration update. This one opens an
incident when files drops below 1:
{ "patch": { "measurements": { "files": { "metricType": "count", "minimum": 1, "mode": "heartbeat", "ruleAction": { "mode": "active", "version": 1 } } } }}The rule kinds are minimum, maximum, maximumAbsoluteDelta,
maximumPercentageDelta, zeroExpectation and ratio. The
publish guide defines each one.
To see what a change would have caught before it goes live, use a draft:
- Create draft. It freezes a window of recent history.
- Update draft with the
current
expectedDraftVersion. Set a rule tonull, or list it indisabledRules, to remove it. - Replay draft to compare the incidents your current and proposed rules produce over that window. Nothing changes.
- Apply draft with
confirm: true,expectedDraftVersionandexpectedConfigurationVersion. If either is stale, you getsignalsitter.precondition_failed. Re-read and try again.
Cancel draft closes a draft without touching the monitor.
Handle incidents
Section titled “Handle incidents”List a monitor’s open incidents:
curl --fail-with-body \ --header "Authorization: Bearer $SIGNALSITTER_API_KEY" \ "$SIGNALSITTER_API_BASE/v1/monitors/nightly-backup/incidents?state=open"States are open, recovering, recovered and resolved.
List all incidents covers every monitor.
- Acknowledge marks it seen without closing it.
- Annotate adds a note to its timeline.
- Resolve closes it.
Each takes an optional {"note": "..."} body. Read what happened with
Get timeline. Check notifications
with List deliveries, and send
one again with Redeliver.
Send notifications with callback endpoints
Section titled “Send notifications with callback endpoints”A callback endpoint is a place alerts go, reusable across monitors: usually a
webhook URL. Changing endpoints needs a manage credential.
curl --fail-with-body \ --request POST \ --header "Authorization: Bearer $SIGNALSITTER_API_KEY" \ --header "Content-Type: application/json" \ --data '{"alias":"operations","url":"https://hooks.example.com/signalsitter"}' \ "$SIGNALSITTER_API_BASE/v1/callbacks"Alerts can also reach people’s phones and messengers through WhooshBang.
Set kind to whooshbang_subscriber and whooshbang.actorId to reach one
person, or to whooshbang_group to reach everyone in your organization. Each
person connects their own phone or messenger in the
dashboard first. See
Create endpoint for the fields.
- The URL must be public HTTPS. A host that resolves to a private or reserved
address is refused with
signalsitter.callback_destination_rejected. - The response includes the signing secret once. Store it to verify deliveries.
- Send test delivery sends one signed
POSTand reports what your receiver answered. - Set default endpoint with
{"endpointId": "..."}, ornullto clear it. - Update endpoint with
"lifecycle": "disabled"stops deliveries."active"starts them again. - Rotate signing secret keeps
the old secret valid for
overlapSeconds, 0 to 900 (default 300). - Remove endpoint is permanent.
A monitor’s callback binding says which endpoint its alerts go to. Set it with Set monitor binding:
binding |
Alerts go to |
|---|---|
{"kind":"organization_default"} |
The default endpoint |
{"kind":"named","alias":"operations"} |
That endpoint |
{"kind":"inline","url":"https://..."} |
A URL used by this monitor only |
{"kind":"disabled"} |
Nowhere |
An inline URL returns its own signing secret once. Add
"rotateSigningSecret": true to replace a lost one.
Get monitor binding shows where
alerts will go today, or why they can’t: no_organization_default,
alias_not_found, endpoint_disabled or endpoint_removed.
Export your data
Section titled “Export your data”curl --fail-with-body \ --header "Authorization: Bearer $SIGNALSITTER_API_KEY" \ --output export.ndjson \ "$SIGNALSITTER_API_BASE/v1/organization/export"- The default is JSON Lines: one record per line, each with a
type. It holds monitors, observations, incidents, endpoint metadata and usage. It never holds secrets. ?format=csvreturns observations only, as a flat table.- If the stream stops, pass the
cursorfrom the last checkpoint record as?cursor=. The last checkpoint hascomplete: true. ?includeObservationBuckets=trueadds summarized history beyond 30 days on a paid plan.- Usage needs a credential with
usage:read. Without it, anomittedrecord says so.
Request conventions
Section titled “Request conventions”- JSON: send bodies as
application/json, up to 16 KiB. Gzip-compressed bodies (Content-Encoding: gzip) are accepted, and the limit applies after decompression. Other encodings get415. - Pagination: lists take
limit(1 to 200, default 50) and returnnextCursor. Pass it back ascursorfor the next page. NonextCursormeans you’re on the last page. - ETags: reads return an
ETag, such as"configuration-3"or"callback-endpoint-3". Changes to monitors, endpoints, credentials and project names need it inIf-Match. A stale tag gets412. A missing one gets428. - Tracing: every response has
X-Request-ID. Send your ownX-Correlation-IDand it comes back on the response.
Errors
Section titled “Errors”Errors are application/problem+json:
{ "type": "/docs/problems/not_found", "title": "Resource not found", "status": 404, "detail": "The requested resource was not found.", "code": "signalsitter.not_found", "correlationId": "example-monitor-1", "retryable": false}Validation failures add fieldErrors. Retryable problems add
retryAfterSeconds and a Retry-After header.
| Status | code |
What to do |
|---|---|---|
| 400 | signalsitter.validation_failed |
Fix the fields named in fieldErrors. |
| 401 | signalsitter.authentication_required |
Send a valid credential. |
| 403 | signalsitter.permission_denied |
Use a credential with the right preset. |
| 404 | signalsitter.not_found |
Check the ID and project. Other organizations’ resources also return this. |
| 409 | signalsitter.conflict |
Re-read the resource, or add ?jurisdiction=. |
| 409 | signalsitter.idempotency_conflict |
Use a new key for a different request. |
| 412 | signalsitter.precondition_failed |
Re-read and send the new ETag. |
| 413 | signalsitter.payload_too_large |
Keep the body under 16 KiB. |
| 415 | signalsitter.unsupported_media_type |
Send application/json, plain or gzip. |
| 428 | signalsitter.precondition_required |
Add If-Match. |
| 429 | signalsitter.rate_limited |
Wait for Retry-After. |
| 503 | signalsitter.temporarily_unavailable |
Wait for Retry-After, then retry. |
Every code has its own page, with what to do: see error codes.
Retry only when retryable is true, after Retry-After. For a write, retry
with the same Idempotency-Key you sent the first time. Never mint a new key
for a retry.
Browser use (CORS)
Section titled “Browser use (CORS)”The API allows credentialed browser requests only from the SignalSitter dashboard. Call it from your server, not from a browser page.