Ingestion Endpoint
The SimpleLogs ingestion endpoint is the single public entry point for sending log events and timing data. You can call it directly from any language or runtime to build your own connector.
Endpoint
POST https://simplelogs.io/api/v1/enqueue
Authentication
Include your API key in the request body as the key field. There are two types of keys:
| Key type | source value | Where to use |
|---|---|---|
Server key (sk_...) | "server" | Server-side code, backend services, workers |
Client key (ck_...) | "client" | Browser code |
The source field in the request body must match the key type — a server key sent with source: "client" (or vice versa) is rejected with 401.
Obtain keys from Settings → API Keys in the SimpleLogs app.
Client key origin restriction
Client key requests are validated against the allowed origins configured for that key. The request must include an Origin header that matches one of the configured origins. Requests without a matching origin receive 403 Forbidden.
Client keys with no configured origins are always rejected — the empty allowlist disables the key.
Request
Headers
| Header | Required | Value |
|---|---|---|
Content-Type | Yes | application/json |
Origin | Required for client keys | The origin of the browser page making the request |
Body
{
"key": "sk_...",
"source": "server",
"timestamp": 1718900000000,
"entries": [
{
"touchpoint": "api/orders/submit",
"message": "Order submitted",
"level": "info",
"timestamp": 1718900000000,
"metadata": {
"orderId": "ord_abc123",
"userId": "usr_xyz"
}
}
]
}
Top-level fields
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Your server or client API key |
source | "server" | "client" | Yes | Must match the key type |
timestamp | number | No | Unix timestamp (ms) for the batch; used as a fallback if individual entries omit timestamp |
entries | Entry[] | Yes | Array of log or timing entries. Minimum 1 entry. |
Entry fields
Each item in entries represents one log event or one timing measurement. You can mix both types in the same request.
| Field | Type | Required | Description |
|---|---|---|---|
touchpoint | string | No | The touchpoint name to associate this entry with |
key | string | No | Correlation key — ties a start to its matching end |
message | string | No | Free-form log message |
level | "info" | "warn" | "error" | "debug" | No | Log severity level (applies to log entries) |
timestamp | number | No | Unix timestamp (ms) when the event occurred |
startTime | number | No | Unix timestamp (ms) — marks this as a timing entry start |
endTime | number | No | Unix timestamp (ms) — marks this as a timing entry end |
duration | number | No | Pre-computed duration in ms. Can be sent with startTime + endTime or alone. |
metadata | object | No | Arbitrary JSON object; keys must be strings |
source | "server" | "client" | No | Per-entry source override |
standalone | boolean | No | When true, the start event is sent immediately without waiting for a matching end |
Entry types
Log event — include message and/or level. Neither startTime nor endTime is required.
{
"touchpoint": "api/users/login",
"message": "Login failed — invalid credentials",
"level": "warn",
"timestamp": 1718900000000,
"metadata": { "email": "user@example.com" }
}
Timing start — include startTime. A matching end entry (same key or touchpoint) closes the measurement.
{
"key": "req-abc123",
"touchpoint": "db/query/products",
"startTime": 1718900000000
}
Timing end / complete — include startTime, endTime, and duration for a fully resolved measurement.
{
"key": "req-abc123",
"touchpoint": "db/query/products",
"startTime": 1718900000000,
"endTime": 1718900000042,
"duration": 42,
"metadata": { "rowsReturned": 128 }
}
Response
Success (202 Accepted)
{ "queued": true }
The 202 status means the payload was accepted and placed in the processing queue. It does not guarantee that every entry will be stored — invalid or duplicate entries may be dropped during processing.
Error responses
| Status | Cause |
|---|---|
400 Bad Request | Request body is not valid JSON |
401 Unauthorized | API key is missing, revoked, or the key type does not match source |
403 Forbidden | Client key request from an origin not in the allowlist |
422 Unprocessable Entity | Request body fails schema validation (see errors in response body) |
503 Service Unavailable | Ingestion queue is temporarily unavailable |
422 response body
{
"message": "Invalid enqueue payload",
"errors": {
"fieldErrors": {
"entries": ["Required"]
},
"formErrors": []
}
}
Examples
curl (server key)
curl -X POST https://simplelogs.io/api/v1/enqueue \
-H "Content-Type: application/json" \
-d '{
"key": "sk_your_server_key",
"source": "server",
"entries": [
{
"touchpoint": "api/orders/submit",
"message": "Order placed",
"level": "info",
"timestamp": '"$(date +%s000)"',
"metadata": { "orderId": "ord_123" }
}
]
}'
Node.js (server key)
async function sendLog(message: string, touchpoint: string) {
await fetch('https://simplelogs.io/api/v1/enqueue', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
key: process.env.SIMPLELOGS_SERVER_KEY,
source: 'server',
entries: [{
touchpoint,
message,
level: 'info',
timestamp: Date.now(),
}],
}),
});
}
Browser (client key)
function sendClientLog(message: string) {
const payload = JSON.stringify({
key: CLIENT_KEY,
source: 'client',
entries: [{
message,
level: 'info',
timestamp: Date.now(),
}],
});
// sendBeacon is preferred for browser — survives page unload
if (navigator.sendBeacon) {
navigator.sendBeacon('https://simplelogs.io/api/v1/enqueue', payload);
} else {
fetch('https://simplelogs.io/api/v1/enqueue', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: payload,
keepalive: true,
});
}
}
Python
import requests
import time
def send_log(message: str, touchpoint: str, level: str = "info"):
requests.post(
"https://simplelogs.io/api/v1/enqueue",
json={
"key": "sk_your_server_key",
"source": "server",
"entries": [{
"touchpoint": touchpoint,
"message": message,
"level": level,
"timestamp": int(time.time() * 1000),
}],
},
timeout=5,
)
Rate limits and quotas
The number of log events you can ingest per month is determined by your plan. See Settings → Billing for current usage and plan limits. The API returns 202 even when you're at your limit — overage behavior (continue ingesting with overage charges vs. drop) depends on your plan configuration.