For external systems and agents
Send status, traffic and events to Manager
Manager receives telemetry from websites, apps, APIs, scheduled jobs and AI agents, and turns it into status, freshness monitoring, thresholds and incidents. The whole integration contract is on this page. You need one secret from a human — the rest you can work out yourself.
POST https://monitor.skjldlabs.com/api/v1/ingest/{SOURCE_ID} Authorization: Bearer {SOURCE_TOKEN} Content-Type: application/json { "idempotency_key": "checkout-health-2026-08-08T13:00", "type": "heartbeat", "status": "healthy", "severity": "info", "title": "Checkout health", "message": "p95 over the last five minutes", "latency_ms": 184, "metric": { "name": "http.latency.p95", "value": 184, "unit": "ms" }, "tags": { "environment": "production", "region": "eu-north-1" }, "data": { "requests": 1294, "version": "4.12.0" }, "occurred_at": "2026-08-08T13:00:00Z" }
{
"accepted": true,
"event_id": "01KZ9F3M2V7X0T4B8Q6R1Y5C3D",
"duplicate": false,
"source_status": "healthy",
"received_at": "2026-08-08T13:00:01+00:00"
}
GET /api/v1/ingest/{SOURCE_ID}/status
{
"source": {
"id": "01KZ8SET41J71MQ261V8N8J2Z1",
"name": "Checkout health",
"type": "health",
"status": "healthy",
"last_received_at": "2026-08-08T13:00:01+00:00",
"next_expected_at": "2026-08-08T13:06:01+00:00",
"expected_interval_seconds": 300
}
}
Same token. This tells you when the next report is expected — miss that window on several consecutive checks and an incident opens.
For machines
The same contract, without the HTML. Fetch this page with Accept: text/markdown and you get agents.md directly.
The model
You always send to a data source, never to a service.
Tenant
The customer or organisation. Owns everything beneath it, and all data is isolated per tenant.
Service
One website, app, API or other digital product.
Data source
One authenticated feed with its own token, its own declared contract and its own expected frequency. This is the address you send to.
What you need before you start
One registration token, created by an owner or admin in the dashboard under "API & MCP". It is bound to one tenant and one stable service key, and it is the only thing you cannot obtain yourself.
Three steps
-
01
Declare yourself
POST a versioned manifest with the registration token. The manifest describes the service and every data source: reporting frequency, metrics, event types, tags and data fields.
-
02
Take the keys
The response carries one single-use token per new data source, along with its REST and MCP address. Store them immediately — they are never shown again.
-
03
Send data
Use the source token against REST or MCP. Both store the same events and drive the same rules.
The manifest
Idempotent. Send it again whenever your service changes: new sources are created, sources you stop declaring are paused with their history intact, and existing tokens are left alone.
{
"manifest_version": "1.0",
"service": {
"key": "customer-portal-production",
"name": "Customer portal",
"environment": "production",
"url": "https://portal.example.com",
"version": "4.12.0",
"accent": "emerald",
"tags": [
"laravel",
"eu-north-1"
]
},
"sources": [
{
"key": "web-analytics",
"name": "Web analytics",
"type": "analytics",
"description": "Traffic aggregated over five-minute windows.",
"expected_interval_seconds": 300,
"grace_seconds": 300,
"metrics": [
{
"name": "analytics.pageviews",
"unit": "count",
"aggregation": "sum",
"description": "Pageviews in the window."
},
{
"name": "analytics.users",
"unit": "count",
"aggregation": "max",
"description": "Unique visitors in the window."
}
],
"events": [
{
"type": "analytics.top_pages",
"severity": "info",
"description": "Most visited pages in the window."
}
],
"tags": [
"site",
"channel"
],
"data_fields": [
{
"name": "top_path",
"type": "string",
"required": false,
"description": "Most visited path in the window."
}
]
}
]
}
Rules
- manifest_version
- MAJOR.MINOR
- service.key
- Lowercase kebab-case. Must match the key the registration token is bound to.
- service.environment
- production · staging · development
- sources[].key
- The stable identity of the feed. Unique within the manifest.
- sources[].type
- health · analytics · contacts · security · laravel-log · performance · custom
- sources[].expected_interval_seconds
- 60 – 2 592 000
- sources[].grace_seconds
- 0 – 86 400
- metrics[].aggregation
- last · sum · avg · min · max · p50 · p95 · p99
- events[].severity
- info · warning · error · critical
Be honest about expected_interval_seconds. Manager opens a "no new data" incident when a feed misses its own window on several consecutive checks. Declaring a cadence you do not keep produces noise, not safety.
{
"registered": true,
"manifest_version": "1.0",
"service": {
"id": "01K…",
"key": "customer-portal-production"
},
"sources": [
{
"key": "web-analytics",
"id": "01KZ8SET41J71MQ261V8N8J2Z1",
"token": "mgr_live_…shown_once",
"ingest_url": "https://monitor.skjldlabs.com/api/v1/ingest/01KZ8SET41J71MQ261V8N8J2Z1",
"mcp_url": "https://monitor.skjldlabs.com/mcp/01KZ8SET41J71MQ261V8N8J2Z1"
}
]
}
REST
HTTPEvery field is optional. Send what you have.
| Field | Type | Notes |
|---|---|---|
| type | string | Free-form kind of report, for example heartbeat, metric, event or log. Max 40 characters. |
| status | string | Service state. down, failed, critical and error open an incident; up, ok, healthy and operational resolve one. Max 40 characters. |
| severity | enum | One of info, warning, error, critical. Defaults to info. |
| title | string | Short headline shown in the event list. Max 255 characters. |
| message | string | Longer context. Max 10 000 characters. |
| latency_ms | integer | Measured latency, 0 to 3 600 000. |
| metric.name | string | Required when metric is present. Stable dot-separated name, max 120 characters. |
| metric.value | number | Required when metric is present. |
| metric.unit | string | For example ms, percent, count or bytes. Max 32 characters. |
| tags | object | Up to 30 string values. Use for dimensions you want to filter on, such as environment or region. |
| data | object | Up to 100 structured values kept with the event. |
| occurred_at | string | ISO 8601. Defaults to now and may not be more than five minutes in the future. |
| idempotency_key | string | Repeating a key for the same source returns the original event instead of storing a duplicate. Max 190 characters. |
Append /batch to the URL and send an events array for up to 100 events in one request.
MCP
Streamable HTTPEvery data source is also a Streamable HTTP MCP server, using the same token. Clients discover the tools automatically.
| Tool | Arguments |
|---|---|
| report_heartbeat Report that the service is alive, with its current status and optional latency. | status, message, latency_ms, idempotency_key |
| report_event Report an operational event such as a deploy, a failure or a signup. | type, title, message, severity, status, tags, data, idempotency_key, occurred_at |
| report_metric Report a numeric metric such as latency, users, errors, conversions or queue depth. | name, value, unit, severity, message, idempotency_key, occurred_at |
| get_source_status Read back the current status of the authenticated source. | none |
manager://source/recent-events
— The most recent events already stored for the authenticated source.
{
"mcpServers": {
"manager-production-health": {
"type": "http",
"url": "https://monitor.skjldlabs.com/mcp/{SOURCE_ID}",
"headers": {
"Authorization": "Bearer {SOURCE_TOKEN}"
}
}
}
}
How often to report
| Source type | Suggested interval | Note |
|---|---|---|
| health | 60s | A heartbeat every minute gives the fastest possible outage detection. |
| performance | 300s | Aggregate latency and error rates over the window before sending. |
| analytics | 300s | Aggregate first. Never send one event per pageview. |
| security | on event | Send when something happens, and a heartbeat in between so silence is distinguishable from calm. |
| contacts | on event | Send each submission as it arrives. |
| laravel-log | on event | Send warnings and above. Batch to stay inside the rate limit. |
Status and severity
status drives incidents: down, failed, critical and error open one, while up, ok, healthy and operational resolve the matching one. severity only controls how loudly a stored event is presented, and opens nothing on its own.
Limits
- Requests per minute, per source
- 120
- Registration requests per minute
- 30
- Events per batch
- 100
- Request body size
- 1.0 MB
- Sources per manifest
- 20
- Metrics per source
- 100
- Declared event types per source
- 50
- Raw event retention
- 90 days
Response codes
- 202
- Accepted and stored.
- 200
- Accepted, but the idempotency key matched an event already stored. Nothing was duplicated.
- 401
- Missing or wrong bearer token, or an invalid or expired signature.
- 404
- Unknown data source id.
- 413
- Request body larger than 1.0 MB.
- 422
- The payload failed validation. The body names the offending fields.
- 423
- The data source is paused in the dashboard.
- 429
- Rate limit exceeded for this source.
Retries and duplicates
Set idempotency_key to something stable and unique per logical report. Repeat the key for the same source and you get the original event back with HTTP 200 and nothing new is stored — so a retry after a timeout is safe.
Optional signing
A source can be set to require signatures. Then every request also needs:
X-Manager-Timestamp: {unix seconds}
X-Manager-Signature: hex(HMAC-SHA256(
timestamp + "." + raw_body, source_token))
The timestamp must be within 300 seconds of server time.
What not to send
Keep secrets, credentials, session tokens and personal data out of title, message, tags and data. Telemetry is retained for 90 days and is visible to everyone with access to the tenant. Aggregate before you send: counts and percentiles, not rows.