Service Level Indicator
observability.calculate_sli
Overview
You send the events, the window and a closed set of good-event predicates, and we return the indicator as a ratio in zero to one, the counts behind it, and the sub-windows that fell below your objective. Every event is good, bad or invalid, and invalid events leave the denominator and are reported, because an availability figure over a denominator that silently shrank is worse than no figure. An empty window is a verdict rather than a failure, so this can be called on a schedule at three in the morning. The window is yours: we read no clock and fetch nothing.
Writing this by hand is where availability numbers go wrong: the denominator quietly drops unparseable events, the boundary is inclusive at both ends so adjacent hours double-count, and the breach test compares an unrounded value against a rounded one.
Reliability
Fully deterministic and clock-free: the same events and window always give the same value, and nothing about the answer depends on when you asked. Rounding is half-up at your scale and the breach comparison uses the rounded value. Failures, none worth retrying unchanged: OBS_SLI_INPUT_MISSING means events or buckets was absent or empty for the kind you chose, so send the one that kind consumes. OBS_SLI_INPUT_AMBIGUOUS means both events and buckets arrived, so send one. OBS_SLI_INPUT_TOO_LARGE means the events or buckets array went past the 2097152-byte inline ceiling, so send fewer entries or fewer fields each. OBS_SLI_WINDOW_INVALID means end was not after start, the span fell outside 1 millisecond to 366 days, or the window object was missing. OBS_SLI_TIMESTAMP_INVALID means a timestamp was absent, unparseable or carried no offset, and the answer names its position. OBS_SLI_OFFSET_MIXED means the list mixed offsets under REQUIRE_UNIFORM or carried a non-UTC one under REQUIRE_UTC, and both positions are named. OBS_SLI_KIND_UNSUPPORTED means the kind is not one of the four. OBS_SLI_PREDICATE_UNSUPPORTED means a predicate type or a status class is not in the vocabulary. OBS_SLI_PREDICATE_INVALID means a predicate had the wrong shape, an empty value list or a threshold out of range. OBS_SLI_SPEC_FIELD_MISSING means kind, goodWhen, latencyField or thresholdMs was required for your kind and absent. OBS_SLI_OBJECTIVE_INVALID means the objective fell outside 0.5 to 0.999999 or carried more than 6 decimal places. OBS_SLI_BUCKET_INVALID means bucketDurationSeconds was missing, out of range or sent without an objective, or a bucket's good exceeded its total. OBS_SLI_BUCKET_LIMIT_EXCEEDED means the window divides into more than 10000 buckets, or more than 10000 counters arrived, so widen the bucket. OBS_SLI_EVENT_LIMIT_EXCEEDED means more than 50000 events arrived, so split the window and combine the counts yourself. OBS_SLI_SAMPLE_NON_FINITE means a latency field carried an infinity or a not-a-number, which is a producer defect rather than an invalid event. OBS_SLI_OPTION_UNSUPPORTED means a property, a scale or an offset policy is not one we accept. OBS_SLI_WORK_REFUSED means the projected work went past 410000 units, where a latency threshold counts as one predicate, and the answer carries the projection and the budget.
Context footprint
Echoes no event; the answer is a value, seven counts and at most 1000 breach entries, so it stays small whatever you send. A successful call costs 2 execution credits.
When to use it
When you hold the events or the counters and want a defensible ratio, its counts, and the sub-windows that breached.
When you want a burn rate against an error budget, a percentile of a latency series, or a deadline on a working calendar.
Scope
Non-goals
- Does not fetch, scrape or store any metric or event
- Does not read a clock — every window boundary arrives in the request
- Does not evaluate a boolean expression language, only allOf over a closed set
- Does not compute a deadline against a working calendar
- Does not report a percentage; every value is a ratio between 0 and 1
Properties
Capabilities
Cost & billing
Flat rate per invocation
File handling
How we handle your data
What this means
This skill may receive sensitive content, but it does not target sensitive data and returns only a structured non-sensitive result.
Squillify guarantees
- Runs inside Squillify
- This skill runs inside Squillify.
- Payloads are not sent to an external service by this skill.
- Squillify does not permanently store submitted payloads.
- Telemetry records metadata only, not submitted content.
- Submitted payloads are not used for training.
- Submitted payloads are not reviewed by humans.
- This skill does not target sensitive data.
- The response does not contain submitted content.
Your responsibility
Squillify does not determine whether submitted content is sensitive. Your application is responsible for deciding what data it is allowed to submit. Your application is responsible for securing the response.
Input schema
| Field | Type | Required | Description |
|---|---|---|---|
| events | array | No | Required unless buckets is provided. 1 to 50000 events each carrying t plus predicate fields. |
| buckets | array | No | Required unless events is provided. 1 to 10000 {t, good, total} counters for AGGREGATE_RATIO. |
| window | object | Yes | Closed half-open window [start, end) as ISO-8601 instants with explicit offsets. |
| sliSpec | object | Yes | Closed indicator spec: kind, 1 to 8 goodWhen predicates, and latencyField plus thresholdMs only for LATENCY_THRESHOLD_COMPLIANCE. |
| objective | number | No | Ratio between 0.5 and 0.999999; omit it for the indicator with no verdict and an empty breaches list. |
| bucketDurationSeconds | integer | No | Required unless objective is omitted. Bucket width from 1 through 86400 seconds. |
| scale | integer | No | Decimal places from 0 through 12; omit it for 6. |
| offsetPolicy | string | No | Offset mixing rule; omit it for REQUIRE_UNIFORM. |
Output schema
| Field | Type | Required | Description |
|---|---|---|---|
| value | number | Yes | Indicator as a ratio between 0 and 1 at your scale, or JSON null when the window held no valid event. |
| verdict | string | Yes | MET, BREACHED, UNDEFINED_EMPTY_WINDOW, UNDEFINED_NO_VALID_EVENTS, or JSON null when you supplied no objective. |
| window | object | Yes | Parsed window as we applied it, with the inclusivity literal and durationSeconds added so a stored verdict names the interval it judged. |
| counts | object | Yes | Closed counters: examined, inWindow, outOfWindow, good, bad, valid and invalid. |
| invalidRatio | number | Yes | Invalid events over in-window events at your scale, or JSON null for an empty window. |
| breaches | array | Yes | Up to 1000 sub-windows below the objective, each {bucketStart, bucketEnd, value, validCount, shortfall}; empty without an objective. |
| summary | object | Yes | Closed publication receipt with kind, optional objective, bucketDurationSeconds, bucketCount, breachCount, breachesTruncated, undefinedBucketCount, scale and roundingMode. |
API contract
POST /v1/executecurl -sS -X POST "$BASE/v1/execute" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{"skillKey":"observability.calculate_sli","input":{"events":[{"t":"2026-07-29T10:00:00Z","status":200}],"window":{"start":"2026-07-29T10:00:00Z","end":"2026-07-29T11:00:00Z"},"sliSpec":{"kind":"AVAILABILITY_RATIO","goodWhen":[{"type":"statusCodeIn","field":"status","values":[200]}]}}}'import { Squillify } from "@squillify/sdk";
const sq = new Squillify({ apiKey: process.env.SQUILLIFY_API_KEY });
const { output } = await sq.execute("observability.calculate_sli", {"events":[{"status":200,"t":"2026-07-29T10:00:00Z"},{"status":200,"t":"2026-07-29T10:15:00Z"},{"status":503,"t":"2026-07-29T10:30:00Z"},{"status":200,"t":"2026-07-29T11:00:00Z"}],"objective":0.99,"window":{"start":"2026-07-29T10:00:00Z","end":"2026-07-29T11:00:00Z"},"sliSpec":{"goodWhen":[{"type":"statusCodeClassIn","field":"status","classes":["S2XX","S3XX"]}],"kind":"AVAILABILITY_RATIO"},"bucketDurationSeconds":1800});Errors
| Code | HTTP | Retryable | Description |
|---|---|---|---|
| VALIDATION_ERROR | 400 | No | The request payload failed schema validation. |
| UNAUTHORIZED | 401 | No | Missing or invalid API key. |
| SKILL_NOT_ACCESSIBLE | 403 | No | The API key is not granted access to this skill. |
| SKILL_NOT_AVAILABLE_FOR_PLAN | 403 | No | This skill is not included in your current plan. |
| PLAN_ELIGIBILITY_UNAVAILABLE | 503 | Yes | Plan eligibility could not be decided. Retry after backoff. |
| SKILL_NOT_FOUND | 404 | No | The requested skill or version does not exist. |
| TENANT_RATE_LIMITED | 429 | Yes | Request rate limit exceeded. Retry after backoff. |
| QUOTA_EXCEEDED | 429 | Yes | Plan quota exhausted. Retryable once the quota period resets. |
| EXECUTION_TIMEOUT | 504 | Yes | Execution exceeded the allowed time budget. |
| OBS_SLI_INPUT_MISSING | 400 | No | The request payload failed this skill's input validation. |
| OBS_SLI_INPUT_AMBIGUOUS | 400 | No | The request payload failed this skill's input validation. |
| OBS_SLI_INPUT_TOO_LARGE | 429 | No | A size, quota, rate, or time limit was exceeded. |
| OBS_SLI_WINDOW_INVALID | 400 | No | The request payload failed this skill's input validation. |
| OBS_SLI_TIMESTAMP_INVALID | 400 | No | The request payload failed this skill's input validation. |
| OBS_SLI_OFFSET_MIXED | 400 | No | The request payload failed this skill's input validation. |
| OBS_SLI_KIND_UNSUPPORTED | 400 | No | The skill does not support the requested variant. |
| OBS_SLI_PREDICATE_UNSUPPORTED | 400 | No | The skill does not support the requested variant. |
| OBS_SLI_PREDICATE_INVALID | 400 | No | The request payload failed this skill's input validation. |
| OBS_SLI_SPEC_FIELD_MISSING | 400 | No | The request payload failed this skill's input validation. |
| OBS_SLI_OBJECTIVE_INVALID | 400 | No | The request payload failed this skill's input validation. |
| OBS_SLI_BUCKET_INVALID | 400 | No | The request payload failed this skill's input validation. |
| OBS_SLI_BUCKET_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| OBS_SLI_EVENT_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| OBS_SLI_SAMPLE_NON_FINITE | 400 | No | The request payload failed this skill's input validation. |
| OBS_SLI_OPTION_UNSUPPORTED | 400 | No | The skill does not support the requested variant. |
| OBS_SLI_WORK_REFUSED | 429 | No | A size, quota, rate, or time limit was exceeded. |
Other limits
| Limit | Value |
|---|---|
| Recommended inline bytes | 32 KiB |
Limitations
- Does not fetch, scrape or store any metric or event
- Does not read a clock — every window boundary arrives in the request
- Does not evaluate a boolean expression language, only allOf over a closed set
- Does not compute a deadline against a working calendar
- Does not report a percentage; every value is a ratio between 0 and 1
- Computing when a support agreement's clock runs out, which business.sla_deadline answers
- Comparing an observed value against a burn-rate alert, which observability.evaluate_slo_burn answers
How to call
POST /v1/execute (requires authentication)
{
"skillKey": "observability.calculate_sli",
"input": {
"events": [
{
"status": 200,
"t": "2026-07-29T10:00:00Z"
},
{
"status": 200,
"t": "2026-07-29T10:15:00Z"
},
{
"status": 503,
"t": "2026-07-29T10:30:00Z"
},
{
"status": 200,
"t": "2026-07-29T11:00:00Z"
}
],
"objective": 0.99,
"window": {
"start": "2026-07-29T10:00:00Z",
"end": "2026-07-29T11:00:00Z"
},
"sliSpec": {
"goodWhen": [
{
"type": "statusCodeClassIn",
"field": "status",
"classes": [
"S2XX",
"S3XX"
]
}
],
"kind": "AVAILABILITY_RATIO"
},
"bucketDurationSeconds": 1800
}
}Examples
Availability over four events, one of them out of the window
{
"events": [
{
"status": 200,
"t": "2026-07-29T10:00:00Z"
},
{
"status": 200,
"t": "2026-07-29T10:15:00Z"
},
{
"status": 503,
"t": "2026-07-29T10:30:00Z"
},
{
"status": 200,
"t": "2026-07-29T11:00:00Z"
}
],
"objective": 0.99,
"window": {
"start": "2026-07-29T10:00:00Z",
"end": "2026-07-29T11:00:00Z"
},
"sliSpec": {
"goodWhen": [
{
"type": "statusCodeClassIn",
"field": "status",
"classes": [
"S2XX",
"S3XX"
]
}
],
"kind": "AVAILABILITY_RATIO"
},
"bucketDurationSeconds": 1800
}{
"value": 0.666667,
"invalidRatio": 0,
"counts": {
"examined": 4,
"inWindow": 3,
"outOfWindow": 1,
"good": 2,
"bad": 1,
"valid": 3,
"invalid": 0
},
"verdict": "BREACHED",
"breaches": [
{
"bucketStart": "2026-07-29T10:30:00Z",
"bucketEnd": "2026-07-29T11:00:00Z",
"value": 0,
"validCount": 1,
"shortfall": 0.99
}
],
"summary": {
"kind": "AVAILABILITY_RATIO",
"objective": 0.99,
"bucketDurationSeconds": 1800,
"bucketCount": 2,
"breachCount": 1,
"breachesTruncated": false,
"undefinedBucketCount": 0,
"scale": 6,
"roundingMode": "HALF_UP"
},
"window": {
"start": "2026-07-29T10:00:00Z",
"end": "2026-07-29T11:00:00Z",
"inclusivity": "[start,end)",
"durationSeconds": 3600
}
}Latency compliance against a threshold, with the breaching half-hour
{
"events": [
{
"latencyMs": 120,
"status": 200,
"t": "2026-07-29T10:00:00Z"
},
{
"latencyMs": 800,
"status": 200,
"t": "2026-07-29T10:10:00Z"
},
{
"latencyMs": 90,
"status": 200,
"t": "2026-07-29T10:20:00Z"
},
{
"latencyMs": 250,
"status": 200,
"t": "2026-07-29T10:40:00Z"
}
],
"objective": 0.95,
"window": {
"start": "2026-07-29T10:00:00Z",
"end": "2026-07-29T11:00:00Z"
},
"sliSpec": {
"kind": "LATENCY_THRESHOLD_COMPLIANCE",
"latencyField": "latencyMs",
"goodWhen": [
{
"type": "statusCodeClassIn",
"field": "status",
"classes": [
"S2XX"
]
}
],
"thresholdMs": 300
},
"bucketDurationSeconds": 1800
}{
"value": 0.75,
"invalidRatio": 0,
"counts": {
"examined": 4,
"inWindow": 4,
"outOfWindow": 0,
"good": 3,
"bad": 1,
"valid": 4,
"invalid": 0
},
"verdict": "BREACHED",
"breaches": [
{
"bucketStart": "2026-07-29T10:00:00Z",
"bucketEnd": "2026-07-29T10:30:00Z",
"value": 0.666667,
"validCount": 3,
"shortfall": 0.283333
}
],
"summary": {
"kind": "LATENCY_THRESHOLD_COMPLIANCE",
"objective": 0.95,
"bucketDurationSeconds": 1800,
"bucketCount": 2,
"breachCount": 1,
"breachesTruncated": false,
"undefinedBucketCount": 0,
"scale": 6,
"roundingMode": "HALF_UP"
},
"window": {
"start": "2026-07-29T10:00:00Z",
"end": "2026-07-29T11:00:00Z",
"inclusivity": "[start,end)",
"durationSeconds": 3600
}
}Recommended use cases
- Turn a batch of request events into an availability figure with visible counts
- Find which five-minute buckets of an hour fell below the objective
Avoid for
- Computing when a support agreement's clock runs out, which business.sla_deadline answers
- Comparing an observed value against a burn-rate alert, which observability.evaluate_slo_burn answers
Related skills
Composable with
Safety & privacy
- Runs without calling any external service.
- Your payloads are never stored permanently.
- Telemetry captures metadata only — never your payload contents.
- We never train models on your payloads.
- No human reviews your payloads.