Skip to main content

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 typesource valueWhere 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

HeaderRequiredValue
Content-TypeYesapplication/json
OriginRequired for client keysThe 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

FieldTypeRequiredDescription
keystringYesYour server or client API key
source"server" | "client"YesMust match the key type
timestampnumberNoUnix timestamp (ms) for the batch; used as a fallback if individual entries omit timestamp
entriesEntry[]YesArray 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.

FieldTypeRequiredDescription
touchpointstringNoThe touchpoint name to associate this entry with
keystringNoCorrelation key — ties a start to its matching end
messagestringNoFree-form log message
level"info" | "warn" | "error" | "debug"NoLog severity level (applies to log entries)
timestampnumberNoUnix timestamp (ms) when the event occurred
startTimenumberNoUnix timestamp (ms) — marks this as a timing entry start
endTimenumberNoUnix timestamp (ms) — marks this as a timing entry end
durationnumberNoPre-computed duration in ms. Can be sent with startTime + endTime or alone.
metadataobjectNoArbitrary JSON object; keys must be strings
source"server" | "client"NoPer-entry source override
standalonebooleanNoWhen 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

StatusCause
400 Bad RequestRequest body is not valid JSON
401 UnauthorizedAPI key is missing, revoked, or the key type does not match source
403 ForbiddenClient key request from an origin not in the allowlist
422 Unprocessable EntityRequest body fails schema validation (see errors in response body)
503 Service UnavailableIngestion 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.