Skip to content

Metrics ingest API

The endpoint a metric source posts to. One header, some JSON, done.

POST https://api.watchtower15.com/api/metric-sources/ingest
Authorization: Bearer wt15ms_…
Content-Type: application/json

The source’s page and the Metrics page in the dashboard show this URL on app.watchtower15.com. Behind a firewall that allows outbound traffic by IP address, use api.watchtower15.com and allow 3.11.48.155, 18.171.1.44 and 18.135.154.177 on port 443: those addresses serve api.watchtower15.com only. See the Restricted egress tab under Install for the list and when it changes.

The body may be any of the following — the format is detected from the content, not from Content-Type:

Form Example
Envelope {"points":[{"metric":"a","value":1}]}
JSON array [{"metric":"a","value":1}]
Single object {"metric":"a","value":1}
NDJSON one JSON object per line
gzip any of the above, gzipped
{ "points": [
{ "metric": "files.uploaded", "value": 1, "recorded_at": "2026-09-16T14:03:00Z" },
{ "metric": "files.uploaded", "value": 1, "recorded_at": "2026-09-16T14:03:07Z" },
{ "metric": "queue.depth", "value": 812, "recorded_at": "2026-09-16T14:03:00Z" },
{ "metric": "etl.last_status", "value": 1, "recorded_at": 1758031380 }
] }

A single point is equally valid — {"metric":"files.uploaded","value":1} — and is fine for a genuinely low-rate publisher. What limits it is the request rate, not correctness: above roughly 20 events a minute, buffer and send a batch.

Field Required Notes
metric one of these two The metric key, as it appears in WatchTower15. A-Z a-z 0-9 . _ -, up to 100 characters. A key we do not recognise is handled by your organisation’s declaration setting — see New metric keys. Keys are scoped to the source whose token you are using.
metric_uuid one of these two The metric’s WatchTower15 UUID, if you would rather not depend on a string. Never creates a metric, and must belong to the source whose token you are using — anything else is an error.
value yes A number. For a level, the measurement — negative and fractional are both fine. For a count, how many occurrences this point represents.
recorded_at no RFC 3339 (2026-09-16T14:03:00Z) or a Unix epoch in seconds or milliseconds, as a number or a string. A time without a timezone is read as UTC, and fractions of a second are kept. Defaults to now. Reaches back as far as your data is kept — see Backfilling history. More than 5 minutes in the future is rejected.

Send either metric or metric_uuid, not both. Sending both with different values is rejected rather than resolved in your favour — we would rather refuse than write to the wrong metric.

Value on a count metric
0 Accepted, and useful. It adds nothing to a total and proves your publisher is alive — silence cannot tell us whether nothing happened or the job died.
Negative Rejected. “−1 files uploaded” is not a thing, and allowing it would let a mistake quietly erase occurrences that already happened.

Levels accept negatives freely — account balances and temperatures need them.

You can send points with times as far back as we keep the data — three years. That covers a publisher that was down for a week, and it covers importing history from whatever you were using before.

There are two allowances, and the line between them is 30 days:

Points Allowance Why
Newer than 30 days 100,000 per metric per rolling 24 hours Alarms can look back at most 30 days, so these points are the ones an alarm may read.
Older than 30 days 2,000,000 per metric per rolling 24 hours Nothing this old can be read by any alarm, so it costs storage and nothing else.

Both are per metric, and both are rolling — use an allowance up at 2pm and it refills at 2pm the next day, not at midnight. A three-year import at one point a minute is about 1.6 million points, so it fits inside a single day’s backfill allowance.

An Idempotency-Key is required for backfill

Section titled “An Idempotency-Key is required for backfill”

Any point older than 30 days must arrive with an Idempotency-Key header, or it is rejected with missing_idempotency_key. Build the key from what you are sending — the metric and the time range, or a chunk number — never from the attempt:

Idempotency-Key: files.uploaded/2024-03-01/chunk-1

That way an import that fails half way and starts again produces the same keys, and the chunks that already landed are ignored instead of written twice. WatchTower15 remembers each key for 30 days: an import restarted within 30 days skips the chunks that landed. After that the key is forgotten, and a chunk sent again under it is written again.

Backfilled points inside the 30-day window are evaluated like any others, so importing history can open incidents. That is deliberate — an alarm that ignored data you supplied would be worse. Import outside business hours if that matters to you, or switch the metric’s alarms off while you import.

Send an Idempotency-Key header and reuse the same value when you retry. The common case is a request that worked at our end and timed out at yours, where retrying is the right instinct; without a key, that retry is counted twice.

Idempotency-Key: 2026-09-16T14:03:00Z/files.uploaded/batch-17

Without one, only retry on a non-2xx response. Retrying never affects your bill — metrics are charged per metric per month, not per point.

{ "status": "success",
"data": { "accepted": true, "batch_id": "…", "points": 3,
"rejected": { "over_metric_cap": 0, "bad_metric_key": 0,
"unknown_metric_uuid": 0, "ambiguous_identifier": 0,
"future_timestamp": 0, "too_old": 0, "non_numeric": 0,
"negative_on_count_metric": 0, "over_point_rate": 0,
"metric_key_deleted": 0 } } }

accepted means the batch is safely queued, not that it is already on a chart. A value typically becomes chartable within about half a minute of being accepted — if you push and immediately reload, give it a moment before concluding it did not arrive.

rejected always lists every reason, at zero if it did not happen. A batch can be partly accepted: if one metric is over its points limit, its points are dropped and the rest of the batch is written. Log these counts. They are the difference between noticing that your clock is five minutes fast and filing a ticket about a missing graph.

Reason What to do
bad_metric_key The key has characters we do not accept. Copy it from the metric’s page.
unknown_metric_uuid The UUID does not exist in your organisation, is switched off, or belongs to a different source than the token you used.
ambiguous_identifier You sent both metric and metric_uuid. Send one.
future_timestamp Your clock is ahead, or you sent a future time. We reject rather than silently move it.
too_old Older than we keep data for this metric — three years.
non_numeric value was not a number, or recorded_at was unparseable (that point alone is rejected; the rest of the batch is accepted). Note a bare year like "2026" is not read as an epoch — only 10-digit-or-longer numbers are.
negative_on_count_metric A count cannot go backwards.
over_point_rate That metric used up its allowance for recent points. Batch, or ask us to raise it.
over_backfill_budget That metric used up its separate, much larger allowance for backfilled points. Wait for it to refill, or ask us to raise it.
missing_idempotency_key You sent backfilled points with no Idempotency-Key header. See Backfilling history — it is required, and it protects you from double-counting.
over_metric_cap You are at your organisation’s metric limit. Delete one, or ask us to raise it.
metric_create_busy A new key arrived while your organisation was busy, or while its metric source was being saved, so its metric was not created this time. Send it again; the next push creates it. Points for existing metrics are not affected.
metric_key_deleted That key belonged to a deleted metric of this source. Create a new metric, or use a new key.
metric_awaiting_approval The metric exists but nobody has accepted it yet. Approve it in Metrics, and points sent after that are recorded. Points sent before are not stored.

What happens when you push a key we have not seen depends on one organisation setting, Require approval for new custom metrics, under Organisation → Settings. It is on by default.

Setting What a new key does
On (default) The metric is created pending. It stores nothing and is not billed. Your push is answered 200 with a metric_awaiting_approval count, so nothing is silently dropped. Someone approves it in Metrics, and recording starts from that moment.
Off The metric is created and starts recording immediately, and is billed from creation.

Billing starts at approval, never at the first push — a metric that waits three days for a decision is not charged for those three days.

Approving is deliberate rather than automatic because each metric is billed monthly. Discarding a pending metric removes it permanently and frees its key for reuse.

A metric belongs to exactly one metric source, and only that source’s token can write to it.

  • Metric keys are unique per source, not per organisation. Two sources can each have a purchase_orders.created; they are separate metrics, with separate history, billed separately.
  • A fleet of machines sharing one token all write to the same metric, however many of them there are. This is the supported way to aggregate across instances — nothing is keyed by machine.
  • Deleting a source deletes every metric it owns, along with their history and alarms.
Code Meaning
200 Accepted. Check points and rejected.
400 The body could not be read, or contained nothing usable.
403 Token missing, unknown, revoked, source paused or deleted, or organisation suspended. Every one of these returns the same response.
413 Too many points, or the body is over the size cap.
429 Rate limited. Retry-After and X-RateLimit-* are set.
Default
Requests per minute, per source 60
Recent points (newer than 30 days), per metric 100,000 per rolling 24h
Backfilled points (older than 30 days), per metric 2,000,000 per rolling 24h
Points per request 1,000
Request body 1 MB (10 MB decompressed)
How far back recorded_at may reach 3 years
Future tolerance 5 minutes
Metrics per organisation 5 by default (the Account Limits card shows yours)

The point allowances are rarely the constraint people expect. A count metric tracking 10,000 events a minute sends one point with "value": 10000 — the point rate has nothing to do with your event rate. 100,000 points a day is a sample roughly every second, finer than any chart draws.

Both point allowances are per metric and rolling: use one up at 2pm and it refills at 2pm tomorrow, not at midnight.

All of these are raisable. Ask.