The published status board
https://forumulate.com/api /public/orgs/{orgId}/spaces/{spaceId}/status overall and each component's status are the REPORTED state, and three things decide it, in this order: a human's declaredState always wins; then the latest check (a failing one is a major_outage); then measured history is allowed to degrade a passing check — under 99.5% uptime reads degraded and under 97% reads partial_outage, so the headline can no longer contradict the percentage printed beside it. History only ever degrades, never improves, and only once window.sufficient is true (at least 20 checks spanning at least 6 hours). window says what each percentage was actually measured over — samples, daysCovered, spanHours and the first/last check — because uptime alone was being printed beside a hardcoded '90 days'. historyDays are the day keys the history arrays line up with, oldest first. outages are the failing runs our own checks recorded, longer than each monitor's failureThreshold, whether or not anybody opened an incident for them.
Parameters
orgIdstringpathrequiredspaceIdstringpathrequiredsharestringquerypreviewstringquerylocalestringqueryforumulate-versionstringheaderforumulate-version: 2026-12-01 on every request from a new integration; it is optional only so that integrations written before versioning keep working. Each version includes everything before it:cURL
curl -X GET "https://forumulate.com/api/public/orgs/{orgId}/spaces/{spaceId}/status?share=&preview=&locale=" \
-H "forumulate-version: <value>"TypeScript
const res = await fetch('https://forumulate.com/api/public/orgs/{orgId}/spaces/{spaceId}/status?share=&preview=&locale=', {
method: 'GET',
headers: {
'forumulate-version': '<value>',
},
});
const data = await res.json();Python
import requests
res = requests.request(
"GET",
"https://forumulate.com/api/public/orgs/{orgId}/spaces/{spaceId}/status?share=&preview=&locale=",
headers={
"forumulate-version": "<value>",
},
)
data = res.json()Go
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://forumulate.com/api/public/orgs/{orgId}/spaces/{spaceId}/status?share=&preview=&locale=", nil)
req.Header.Set("forumulate-version", "<value>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}PHP
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://forumulate.com/api/public/orgs/{orgId}/spaces/{spaceId}/status?share=&preview=&locale=');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['forumulate-version: <value>']);
$response = curl_exec($ch);
curl_close($ch);
echo $response;Ruby
require 'net/http'
require 'uri'
uri = URI('https://forumulate.com/api/public/orgs/{orgId}/spaces/{spaceId}/status?share=&preview=&locale=')
req = Net::HTTP::Get.new(uri)
req['forumulate-version'] = '<value>'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(req)
end
puts res.bodyJava
import java.net.URI;
import java.net.http.*;
import java.net.http.HttpRequest.BodyPublishers;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://forumulate.com/api/public/orgs/{orgId}/spaces/{spaceId}/status?share=&preview=&locale="))
.header("forumulate-version", "<value>")
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());Responses
Example
{
"brand": "string",
"dayThresholds": {
"operational": 0,
"partial": 0
},
"frontPage": {
"aside": {
"doc": {
"content": null,
"type": null
},
"phone": "after",
"width": "default"
},
"customization": {},
"doc": {
"content": [
null
],
"type": "doc"
},
"gap": "lg",
"hideTitle": true,
"paddingBottom": "string",
"paddingTop": "string",
"preset": "string",
"sidebar": {
"divider": {
"color": null,
"style": null,
"width": null
},
"doc": {
"content": null,
"type": null
},
"gap": null,
"phone": null,
"position": "left",
"source": "doc",
"startAfter": 0,
"width": "default"
},
"width": 0
},
"groups": [
{
"collapsed": true,
"description": "string",
"name": "string",
"parent": "string"
}
],
"historyDays": [
"string"
],
"homeDoc": {},
"homeDocTranslations": {},
"incidents": [
{
"audience": {
"groups": [
null
],
"who": "everyone"
},
"autoComplete": true,
"autoStart": true,
"body": {},
"components": [
{
"impact": null,
"monitorId": null,
"name": null,
"slug": null
}
],
"fields": {},
"id": "string",
"locale": "string",
"machineTranslated": true,
"maintenanceState": "completed",
"resolvedAt": "string",
"retroactive": true,
"scheduledFor": "string",
"scheduledUntil": "string",
"severity": "string",
"startedAt": "string",
"state": "string",
"title": "string",
"updatedAt": "string",
"updates": [
{
"body": null,
"createdAt": null,
"id": null,
"state": null
}
]
}
],
"languages": [
"string"
],
"monitors": [
{
"declaredState": "degraded",
"description": "string",
"display": "bars",
"failingChecks": 0,
"group": "string",
"history": [
null
],
"historyImpact": [
"degraded"
],
"historyState": [
"operational"
],
"historyUptime": [
null
],
"id": "string",
"incidentImpact": "degraded",
"kind": "heartbeat",
"latencyMs": 0,
"linkUrl": "string",
"name": "string",
"region": "string",
"slug": "string",
"startsOn": "string",
"state": "down",
"status": "degraded",
"target": {
"name": null,
"spaceId": null,
"url": null
},
"uptime": 0,
"window": {
"daysCovered": 0,
"firstCheckAt": null,
"lastCheckAt": null,
"minSamples": 0,
"minSpanHours": 0,
"reportedDays": 0,
"samples": 0,
"spanHours": 0,
"sufficient": true
}
}
],
"outages": [
{
"endedAt": "string",
"failures": 0,
"monitorId": "string",
"monitorName": "string",
"ongoing": true,
"startedAt": "string"
}
],
"overall": "degraded",
"partnerMaintenance": [
{
"component": "string",
"id": "string",
"monitorId": "string",
"name": "string",
"scheduledFor": "string",
"scheduledUntil": "string",
"status": "string",
"url": "string"
}
],
"status": "string",
"window": {
"daysCovered": 0,
"firstCheckAt": "string",
"lastCheckAt": "string",
"minSamples": 0,
"minSpanHours": 0,
"reportedDays": 0,
"samples": 0,
"spanHours": 0,
"sufficient": true
}
}Schema
brandstringrequireddayThresholdsobjectrequiredShow child parameters
operationalnumberrequiredpartialnumberrequiredfrontPageStoredFrontPagerequiredShow child parameters
asideobjectShow child parameters
docobjectrequiredphonestringwidthstringcustomizationobjectdocobjectrequiredShow child parameters
contentobject[]requiredtypestringrequiredgapstringhideTitlebooleanpaddingBottomstring | numberpaddingTopstring | number | IndexFrameStepspresetstringsidebarobjectShow child parameters
dividerobjectdocobjectrequiredgapstring | numberphonestring | objectpositionstringrequiredsourcestringstartAfternumberwidthstringwidthnumber | stringgroupsobject[]requiredShow child parameters
collapsedbooleandescriptionstringnamestringrequiredparentstringhistoryDaysstring[]requiredhomeDocrequiredhomeDocTranslationsobjectincidentsIncidentSummary[]requiredShow child parameters
audienceobjectShow child parameters
groupsstring[]requiredwhostringrequiredautoCompletebooleanrequiredautoStartbooleanrequiredbodyobjectrequiredcomponentsAffectedComponent[]requiredShow child parameters
impactstringrequiredmonitorIdstringrequirednamestringrequiredslugstringrequiredfieldsobjectrequiredidstringrequiredlocalestringmachineTranslatedbooleanmaintenanceStatestringrequiredresolvedAtstringrequiredretroactivebooleanrequiredscheduledForstringrequiredscheduledUntilstringrequiredseveritystringrequiredstartedAtstringrequiredstatestringrequiredtitlestringrequiredupdatedAtstringrequiredupdatesIncidentUpdate[]requiredShow child parameters
bodystringrequiredcreatedAtstringrequiredidstringstatestringrequiredlanguagesstring[]requiredmonitorsMonitorState[]requiredShow child parameters
declaredStatestringrequireddescriptionstringrequireddisplaystringrequiredfailingChecksnumberrequiredgroupstringrequiredhistoryboolean[]requiredhistoryImpactstring[]requiredhistoryStatestring[]requiredhistoryUptimenumber[]requiredidstringrequiredincidentImpactstringrequiredkindstringrequiredlatencyMsnumberrequiredlinkUrlstringrequirednamestringrequiredregionstringrequiredslugstringrequiredstartsOnstringrequiredstatestringrequiredstatusstringrequiredtargetobjectrequiredShow child parameters
namestringrequiredspaceIdstringrequiredurlstringuptimenumberrequiredwindowobjectrequiredShow child parameters
daysCoverednumberrequiredfirstCheckAtstringrequiredlastCheckAtstringrequiredminSamplesnumberrequiredminSpanHoursnumberrequiredreportedDaysnumberrequiredsamplesnumberrequiredspanHoursnumberrequiredsufficientbooleanrequiredoutagesOutageWindow[]requiredShow child parameters
endedAtstringrequiredfailuresnumberrequiredmonitorIdstringrequiredmonitorNamestringrequiredongoingbooleanrequiredstartedAtstringrequiredoverallstringrequiredpartnerMaintenanceStatusPartnerMaintenance[]Show child parameters
componentstringrequiredidstringrequiredmonitorIdstringrequirednamestringrequiredscheduledForstringrequiredscheduledUntilstringrequiredstatusstringrequiredurlstringrequiredstatusstringrequiredwindowobjectrequiredShow child parameters
daysCoverednumberrequiredfirstCheckAtstringrequiredlastCheckAtstringrequiredminSamplesnumberrequiredminSpanHoursnumberrequiredreportedDaysnumberrequiredsamplesnumberrequiredspanHoursnumberrequiredsufficientbooleanrequiredrequest.malformed). A value that was understood but is not acceptable is a 422 validation.failed.Example
{
"error": {
"code": "string",
"message": "string",
"details": {},
"docs": "string",
"reason": "string",
"slugs": [
"string"
],
"conflict": {
"etag": "string",
"ops": [
null
]
}
},
"requestId": "string"
}Schema
errorobjectrequiredShow child parameters
codestringrequiredrbac.forbidden.messagestringrequireddetailsobjectdocsstringGET /space-types/{type}/presets. Present on errors over a controlled list, so the answer to "what am I allowed to send?" is in the error itself.reasonstringiat_too_old, iat_in_future, bad_signature, bad_issuer, bad_audience, expired and more. Branch on this, not on message.slugsstring[]space.slug_taken, page.slug_taken, collection.slug_taken, site.slug_taken or badge.slug_taken.conflictobjectPATCH …/entries/{entryId}/fields) whose If-Match was stale.Show child parameters
etagstringrequiredopsarrayrequiredIf-Match that touch a path your own operations touch. Empty: none did — retry the batch unchanged under etag. null: the history no longer reaches your tag — re-read the entry first.requestIdstringx-request-id and x-forumulate-request-id response headers. Quote it in a bug report: it is the only thing that finds your exact request in our logs. It is in the body because that is the part that survives — an SDK that throws the parsed body, a serverless log that keeps the body and drops the headers, a ticket with the JSON pasted in.*.not_found).Example
{
"error": {
"code": "string",
"message": "string",
"details": {},
"docs": "string",
"reason": "string",
"slugs": [
"string"
],
"conflict": {
"etag": "string",
"ops": [
null
]
}
},
"requestId": "string"
}Schema
errorobjectrequiredShow child parameters
codestringrequiredrbac.forbidden.messagestringrequireddetailsobjectdocsstringGET /space-types/{type}/presets. Present on errors over a controlled list, so the answer to "what am I allowed to send?" is in the error itself.reasonstringiat_too_old, iat_in_future, bad_signature, bad_issuer, bad_audience, expired and more. Branch on this, not on message.slugsstring[]space.slug_taken, page.slug_taken, collection.slug_taken, site.slug_taken or badge.slug_taken.conflictobjectPATCH …/entries/{entryId}/fields) whose If-Match was stale.Show child parameters
etagstringrequiredopsarrayrequiredIf-Match that touch a path your own operations touch. Empty: none did — retry the batch unchanged under etag. null: the history no longer reaches your tag — re-read the entry first.requestIdstringx-request-id and x-forumulate-request-id response headers. Quote it in a bug report: it is the only thing that finds your exact request in our logs. It is in the body because that is the part that survives — an SDK that throws the parsed body, a serverless log that keeps the body and drops the headers, a ticket with the JSON pasted in.Example
{
"error": {
"code": "string",
"message": "string",
"details": {},
"docs": "string",
"reason": "string",
"slugs": [
"string"
],
"conflict": {
"etag": "string",
"ops": [
null
]
}
},
"requestId": "string"
}Schema
errorobjectrequiredShow child parameters
codestringrequiredrbac.forbidden.messagestringrequireddetailsobjectdocsstringGET /space-types/{type}/presets. Present on errors over a controlled list, so the answer to "what am I allowed to send?" is in the error itself.reasonstringiat_too_old, iat_in_future, bad_signature, bad_issuer, bad_audience, expired and more. Branch on this, not on message.slugsstring[]space.slug_taken, page.slug_taken, collection.slug_taken, site.slug_taken or badge.slug_taken.conflictobjectPATCH …/entries/{entryId}/fields) whose If-Match was stale.Show child parameters
etagstringrequiredopsarrayrequiredIf-Match that touch a path your own operations touch. Empty: none did — retry the batch unchanged under etag. null: the history no longer reaches your tag — re-read the entry first.requestIdstringx-request-id and x-forumulate-request-id response headers. Quote it in a bug report: it is the only thing that finds your exact request in our logs. It is in the body because that is the part that survives — an SDK that throws the parsed body, a serverless log that keeps the body and drops the headers, a ticket with the JSON pasted in.Example
{
"error": {
"code": "string",
"message": "string",
"details": {},
"docs": "string",
"reason": "string",
"slugs": [
"string"
],
"conflict": {
"etag": "string",
"ops": [
null
]
}
},
"requestId": "string"
}Schema
errorobjectrequiredShow child parameters
codestringrequiredrbac.forbidden.messagestringrequireddetailsobjectdocsstringGET /space-types/{type}/presets. Present on errors over a controlled list, so the answer to "what am I allowed to send?" is in the error itself.reasonstringiat_too_old, iat_in_future, bad_signature, bad_issuer, bad_audience, expired and more. Branch on this, not on message.slugsstring[]space.slug_taken, page.slug_taken, collection.slug_taken, site.slug_taken or badge.slug_taken.conflictobjectPATCH …/entries/{entryId}/fields) whose If-Match was stale.Show child parameters
etagstringrequiredopsarrayrequiredIf-Match that touch a path your own operations touch. Empty: none did — retry the batch unchanged under etag. null: the history no longer reaches your tag — re-read the entry first.requestIdstringx-request-id and x-forumulate-request-id response headers. Quote it in a bug report: it is the only thing that finds your exact request in our logs. It is in the body because that is the part that survives — an SDK that throws the parsed body, a serverless log that keeps the body and drops the headers, a ticket with the JSON pasted in.