HTTP without an SDK
No SDK needed: anything that can make an HTTP request can send events and log lines to Bugtail. A shell script, a cron job, a log shipper or a language without a Sentry SDK.
Use a collector with the Bugtail native protocol, created under Settings > Projects and collectors. Its key goes in the URL.
Endpoints
POST https://ingest.bugtail.eu/ingest/<key>/events
POST https://ingest.bugtail.eu/ingest/<key>/logs
POST https://ingest.bugtail.eu/ingest/<key>/batch
/events and /logs take any of these:
- a single JSON object
- a JSON array of objects
- an object wrapping the list:
{"events": [...]}or{"logs": [...]} - NDJSON, one object per line, with
Content-Type: application/x-ndjson
Bodies may be compressed with gzip or deflate.
Events
An event becomes an issue, grouped with earlier events of the same error.
curl -X POST https://ingest.bugtail.eu/ingest/<key>/events \
-H "Content-Type: application/json" \
-d '{
"message": "Sync failed",
"level": "error",
"environment": "production",
"release": "1.4.0",
"user": {"id": 4821},
"exception": {
"type": "SyncException",
"value": "Remote refused",
"frames": [
{"file": "/app/Jobs/Sync.php", "line": 20, "function": "handle"}
]
}
}'
| Field | Meaning |
|---|---|
message |
What happened. Used as the title when there is no exception. |
level |
debug, info, warning, error or fatal. Defaults to error. |
exception |
type, value and a list of frames with file, line and function, innermost last. |
environment, release |
For filtering, and for releases. |
user |
An object with an id, email or username. Counted as users affected. |
fingerprint |
A list of strings that overrides grouping. See Grouping. |
timestamp |
Unix seconds or an ISO 8601 date. Defaults to the time it arrives. |
event_id |
Optional. An id that was recorded before is ignored, so retries never count twice. |
Log lines
Log lines are shown under Logs. The shape is the one a Monolog handler writes, so a log shipper usually needs no mapping:
curl -X POST https://ingest.bugtail.eu/ingest/<key>/logs \
-H "Content-Type: application/x-ndjson" \
--data-binary $'{"level":"info","message":"Backup started"}\n{"level":"error","message":"Backup failed","context":{"disk":"/var"}}'
| Field | Meaning |
|---|---|
message |
The line itself. |
level |
A level name, or a Monolog level number. |
channel |
Where it came from, such as database or billing. |
context |
An object with anything else worth keeping. |
environment, release |
For filtering. |
trace_id, span_id |
Links the line to a traced operation. |
timestamp |
Unix seconds or an ISO 8601 date. Defaults to the time it arrives. |
Several kinds at once
/batch takes one object with a list per kind. Every list is optional:
{
"events": [],
"logs": [],
"metrics": []
}
Events and log lines have the shapes above. A browser can send a batch with navigator.sendBeacon(), which is why the key is in the URL: a beacon cannot set headers. Browsers refuse beacons over about 64 KB, so split larger batches.
Metrics
Each metric in the batch is one reading:
{
"name": "checkout.duration",
"kind": "histogram",
"timestamp": 1756296000.5,
"value": 1234.5,
"count": 12,
"min": 40,
"max": 300,
"unit": "ms",
"attributes": {"route": "/checkout"}
}
kind is gauge, sum or histogram. A histogram carries the sum of its observations in value and their number in count. Readings are deltas: what happened since the previous send, not a running total. Send "temporality": "cumulative" for a running total. Attributes are what a series is split on, so keep them to a small set of values.
Responses
| Status | Meaning |
|---|---|
202 |
Accepted. It shows up in the dashboard within seconds. |
401 |
The key is unknown. |
403 |
The collector is disabled, the key was rotated, or the site is not in the allowed origins. |
413 |
The body is larger than the collector allows. |
429 |
Too many requests. Wait for the number of seconds in Retry-After. |