# Ingest

Source: https://www.pingtower.com/docs/service/ingest/

One endpoint, one JSON object per line, one bearer token per source. Everything Pingtower knows starts here.

<span class="pill post">POST</span> `/v1/ingest`, authenticated with a `pti_` source token. Nothing in the body names a project or a source; the token decides.

## Payload

| Field | Type | Meaning |
| --- | --- | --- |
| `message`<span class="req">REQUIRED</span> | string | The line. Template extraction runs on this. Up to 64 KiB. |
| `level` | integer | Severity. 100 debug, 200 info, 300 warn, 400 error, 500 critical. |
| `keys` | object | Up to 128 named values. Each value is substituted out of the message to form the template, is available to rule conditions, and can group alerts through `identifier_keys`. Scalars only; they are stored as strings. |
| `tags` | string[] | Up to 64 free-form labels a rule can match on. |

```json
{
  "message": "payment gateway timeout after 30012ms for order o_48113",
  "level": 400,
  "keys": { "order": "o_48113", "gateway": "stripe", "latency_ms": 30012 },
  "tags": ["billing", "prod"]
}
```

The server timestamps the line on arrival. There is no client timestamp field.

## Response

`202 Accepted` with `{"alerted": true|false, "template_id": "…"}`. `alerted` says whether any rule fired on this line, which makes a one-line smoke test possible. `template_id` is the SHA-256 of the recovered template.

| Status | Means |
| --- | --- |
| `401` | The bearer is missing, malformed, or not an ingest token. |
| `429` | Over the plan's ingest rate, or the tenant's storage quota. `Retry-After` says how long to wait. |
| `500` | The daemon could not store the line. |

## What happens to a line

1. **Template extraction.** Every value in `keys` is found in the message and replaced by `{{ .name }}`. Longest value first, every occurrence, so `order o_48113` and `order o_48120` share the template `order {{ .order }}`. A message with no key hits is its own template.
2. **Rule evaluation.** Every rule in the project is evaluated against the line's level, source, tags, template and keys.
3. **Dedup.** A line whose template matches an alert that is already firing bumps that alert's count instead of opening a new one. See [Rules](/docs/service/rules/#identity) for how `identifier_keys` split that.
4. **Retention.** If the source has `retain_logs`, the raw line is kept so the [log tail](/docs/service/logtail/) can show it. Retention is capped by plan and by the daemon's flags; a cap costs you history, never an alert.

<div class="docs-callout note"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="8" cy="8" r="6.5"/><path d="M8 7v4M8 5v.5"/></svg><div><span class="t">Only what you name is masked</span>Template extraction replaces the values you passed as <code>keys</code>, nothing else. Put every variable part of the message, ids, durations, hosts, in <code>keys</code>, or two lines that differ only there will open two alerts.</div>
</div>


## Batching and backpressure

One object per request. The endpoint does very little work per call, so batching gains nothing and loses the per-line `alerted` answer. Over the rate you get `429` and a `Retry-After`. The [addons agent](/docs/addons/) spools to disk and drains when the tower is reachable again; if you write your own sender, do the same rather than dropping lines.
