Goals
A goal defines target thresholds/triggers against a metric.
A goal defines target thresholds/triggers against a metric.
Full field reference (types, enums, required/read-only flags) is generated from components.schemas.Goal in the API Reference tab, and tabulated field by field — with what each one's read-only status means for a write — in Field Reference. Or fetch the live OpenAPI document directly, see Getting Started for the URL.
GET /goals · GET /goals/:goalId
GET /goals · GET /goals/:goalId{
"id": "ee3ed006c5734ec6895c640607a5ebee",
"name": "MRR target",
"type": "THRESHOLD",
"ownerId": "4274514cd27b485e8d4630dd611c01e5",
"payload": { "value": 50000, "thresholdType": "ABOVE", "valueType": "VALUE" },
"scopes": { "metrics": ["a5b92ef1de2b4a78b1fbab5ee0340113"] },
"triggers": [{ "type": "event.updated.metric.data" }],
"createdAt": "2026-02-01T00:00:00.000Z",
"updatedAt": "2026-02-10T00:00:00.000Z",
"version": "1.0"
}type is one of THRESHOLD, RECURRING_TARGET, CUSTOM_TARGET, RANGE, and determines payload's shape. triggers[].type is one of event.updated.metric.data (fires on new metric data) or event.updated.schedule.period_end (fires on a recurring schedule) — not a threshold direction; payload.thresholdType (ABOVE/BELOW) is what carries that.
payload by type
payload by typenotifyOptions, wherever it appears below, is an array of strings — even when it holds a single option.
THRESHOLD — a single value compared against a fixed threshold:
{ "value": 50000, "thresholdType": "ABOVE", "valueType": "VALUE" }RANGE — a lower/upper bound instead of a scalar value:
{ "lowerValue": 40000, "upperValue": 60000, "notifyOptions": ["CROSSED_OUTSIDE"] }RECURRING_TARGET — a target re-evaluated every period:
{ "value": 50000, "recurringPeriod": "M" }recurringPeriod is D (daily), w (weekly) or M (monthly) — note that the weekly code is lower-case while the other two are upper-case.
CUSTOM_TARGET — an explicit target per period, rather than one recurring value:
{
"customGranularity": "M",
"values": [
{ "coordinate": "2026-01-01T00:00:00.000Z", "value": 45000 },
{ "coordinate": "2026-02-01T00:00:00.000Z", "value": 47000 }
]
}POST /goals
POST /goalsname and type are required. Returns 201 with the meta/data envelope. A metric scope (scopes.metrics) is practically required too — creating a goal without one fails with 403 on the share-rights check. When a metric scope is present, its id is attached to each trigger internally.
triggers itself is optional, but each entry you do send must name its type — that is the whole of a public trigger, so an entry without one is rejected with 400.
PUT /goals/:goalId
PUT /goals/:goalIdReplace editable fields; returns the updated goal. To change a threshold, fetch the goal, edit the value under payload, and send it back — the read-only fields the GET returned (id, ownerId, createdAt, updatedAt) are ignored as long as id/ownerId still match the stored goal, so there is nothing to strip out first. Sending a different id or ownerId is rejected with 400:
{
"name": "MRR target",
"type": "THRESHOLD",
"payload": { "value": 60000, "thresholdType": "ABOVE", "valueType": "VALUE" },
"scopes": { "metrics": ["a5b92ef1de2b4a78b1fbab5ee0340113"] },
"triggers": [{ "type": "event.updated.metric.data" }]
}This is a replace, not a merge, at every level — send the goal back whole, with your edit applied, exactly as the GET returned it. In particular payload is replaced as a unit, so a body carrying only { "value": 60000 } drops thresholdType and valueType; and an omitted scopes or triggers is cleared rather than kept. name and type are required on every write.
DELETE /goals/:goalId
DELETE /goals/:goalId204 No Content.
Updated 6 days ago