Appearance
Ingest
Send log entries to Krafter. These endpoints use a project ingest token for authentication, not your team API key.
Base URL: https://app.krafter.dev/api/v1
Authentication
Ingest endpoints require a project ingest token (Authorization: Bearer <ingest_token>), not a team API key. Ingest tokens are generated when you create a project and can be rotated at any time.
Rate limit (log_ingest)
Both POST /logs/ingest and POST /logs/ingest/batch use the dedicated log_ingest bucket — 300 requests / 60 seconds per project ingest token. The bucket counts HTTP requests, not log entries: a single batch of 1,000 NDJSON lines is one request. To exceed 300 entries / minute, switch from single-entry posting to batch ingest.
Single Log Entry
Send a single log entry as JSON.
POST /logs/ingestAuthentication: Project ingest token
Content-Type: application/json
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | The log message text. |
level | string | No | Log severity: debug, info, warn, error, or fatal. Defaults to "info". |
stream | string | No | Logical grouping for the log entry (e.g., "backend", "auth"). Auto-creates the stream if it does not exist. |
service | string | No | Name of the service that produced the log. |
trace_id | string | No | Distributed trace identifier. |
span_id | string | No | Span identifier within a trace. |
metadata | object | No | Arbitrary key-value pairs for structured data. |
Example Request
bash
curl -X POST https://app.krafter.dev/api/v1/logs/ingest \
-H "Authorization: Bearer krl_live_kp7tx2bm4qrs6vwy3jnh5zfd7acmeg2j" \
-H "Content-Type: application/json" \
-d '{
"level": "error",
"message": "Failed to process payment",
"stream": "payments",
"service": "billing-service",
"trace_id": "tr_abc123",
"span_id": "sp_456",
"metadata": {
"order_id": "ord_789",
"amount": 49.99,
"currency": "EUR"
}
}'Example Response
json
// 202 Accepted
{
"ok": true
}Minimal Example
Only the message field is required. All other fields are optional:
bash
curl -X POST https://app.krafter.dev/api/v1/logs/ingest \
-H "Authorization: Bearer krl_live_kp7tx2bm4qrs6vwy3jnh5zfd7acmeg2j" \
-H "Content-Type: application/json" \
-d '{"message": "Application started"}'Responses
| Status | Body | When |
|---|---|---|
202 | {"ok": true} | Entry accepted and persisted. |
422 | {"error": "message is required"} | message field missing or empty. |
422 | {"error": "level must be one of: debug, info, warn, error, fatal"} | level was supplied but is not one of the five allowed values. |
422 | {"error": "Stream limit exceeded for this project (max 10000 streams)"} | The entry references a stream name that does not yet exist and the project has already reached the per-project cap. Existing streams continue to accept entries. |
429 | {"error": "Daily log ingestion limit exceeded"} | The project's daily_limit_mb has been reached for today. |
429 | {"error": "Rate limit exceeded", "retry_after": <seconds>} | The log_ingest bucket cap (300 req / 60s) was hit. Includes a retry-after header. |
503 | {"error": "Log storage temporarily unavailable"} | ClickHouse insert failed. Safe to retry after a brief backoff. |
Stream cardinality
Each project may auto-create up to 10,000 distinct stream names. The cap exists to stop accidental high-cardinality usage (e.g. embedding request IDs into the stream field) from bloating dashboards. Stream names are intended to be logical groupings like "backend", "worker", "auth" — not per-request identifiers. Use the metadata object for high-cardinality data instead.
Batch Ingest
Send multiple log entries in a single request using NDJSON (Newline-Delimited JSON) format. Each line is a separate JSON object representing one log entry.
POST /logs/ingest/batchAuthentication: Project ingest token
Content-Type: application/x-ndjson
Maximum request size: 5 MB
Request Body
One JSON object per line. Each object follows the same schema as the single log entry. Lines are separated by newline characters (\n). Do not use a trailing newline or wrapping array.
Example Request
bash
curl -X POST https://app.krafter.dev/api/v1/logs/ingest/batch \
-H "Authorization: Bearer krl_live_kp7tx2bm4qrs6vwy3jnh5zfd7acmeg2j" \
-H "Content-Type: application/x-ndjson" \
-d '{"level":"info","message":"Request received","stream":"backend","service":"api-server","trace_id":"tr_001"}
{"level":"info","message":"Authenticated user usr_42","stream":"backend","service":"api-server","trace_id":"tr_001"}
{"level":"info","message":"Query executed in 12ms","stream":"backend","service":"api-server","trace_id":"tr_001"}
{"level":"info","message":"Response sent 200 OK","stream":"backend","service":"api-server","trace_id":"tr_001"}'Example Response
json
// 202 Accepted
{
"ok": true,
"count": 4,
"dropped": 0
}The count field confirms how many log entries were accepted from the batch. The dropped field reports how many lines were rejected (malformed JSON or schema validation failures) — count + dropped always equals the number of non-empty lines you sent (capped at 10,000).
Best-effort delivery
The batch endpoint is best-effort: lines that fail to JSON-decode or fail validation (missing message, invalid level) are dropped without a per-line error. The dropped field in the response tells you how many were rejected, but not which ones or why.
If dropped > 0, inspect your batch payload for malformed JSON or invalid field values. If you need per-entry delivery confirmation, use the single-entry endpoint instead — it returns a 422 per failing entry so you can react to each one.
Responses
| Status | Body | When |
|---|---|---|
202 | {"ok": true, "count": N, "dropped": D} | Batch accepted. N is the number of successfully validated and persisted entries; D is the number of lines that failed JSON decoding or schema validation. N + D equals the number of non-empty lines you sent (capped at 10,000). |
202 | {"ok": true, "count": 0, "dropped": D} | Either the body was empty (D = 0) or every line failed JSON decoding / validation (D > 0). The request itself succeeded — nothing was persisted. |
413 | {"error": "Payload too large (max 5MB)"} | Request body exceeded 5 MB. |
422 | {"error": "Stream limit exceeded for this project (max 10000 streams)"} | The batch references one or more stream names that do not yet exist and the project has already reached the per-project cap. The whole batch is rejected — split it so that only existing stream names remain. |
429 | {"error": "Daily log ingestion limit exceeded"} | The project's daily_limit_mb has been reached for today. |
429 | {"error": "Rate limit exceeded", "retry_after": <seconds>} | The log_ingest bucket cap (300 req / 60s) was hit. Includes a retry-after header. |
503 | {"error": "Log storage temporarily unavailable"} | ClickHouse insert failed. Safe to retry after a brief backoff. |
NDJSON Format
NDJSON (Newline-Delimited JSON) is a format where each line is a standalone JSON object:
{"level":"info","message":"First log entry","stream":"app"}
{"level":"warn","message":"Second log entry","stream":"app"}
{"level":"error","message":"Third log entry","stream":"app"}Rules:
- Each line must be valid JSON on its own
- Lines are separated by
\n(newline) - No commas between lines
- No wrapping array or object
- No trailing newline required
TIP
For high-throughput applications, buffer log entries and send them in batches of 100--1,000 entries. This reduces HTTP overhead significantly compared to sending entries one at a time.