Send a project's API, Auth, REST, Postgres and Edge Function logs to Datadog, Loki (or Grafana Cloud) or your own https endpoint.
| Limit | Free | Pro | Scale | Enterprise |
|---|---|---|---|---|
| Log drains per project | 0 | 1 | 3 | 10 |
A log drain sends your project's logs to your own log service as well, about a minute after they happen. They are the same lines you search in the dashboard, and they stay searchable there. Open your project, choose Logs → Log drains and Add log drain. Owners and admins of the project can manage them.
#Destinations
| Send to | What you enter | What arrives |
|---|---|---|
| Datadog | Your Datadog site (datadoghq.com, datadoghq.eu, us3, us5, ap1, gov) and an API key; optionally a service name and tags | Lines through the Logs intake API with ddsource:stitchdb, service:stitchdb-<source> (or your service), host = project ref, the level as status, tags project:<ref> and source:<source>, and every field under stitchdb.* |
| Loki / Grafana Cloud | The Loki URL, and a user + token (Grafana Cloud) or a bearer token; optionally a tenant | Streams labelled service_name="stitchdb", project, source and level; each line is JSON with message and the source's fields |
| HTTPS endpoint | An https URL and up to 10 extra headers | A POST per batch: a gzip'ed JSON array of lines, signed (below) |
For each drain you choose the sources (API requests, Auth, REST server, Postgres, Edge Functions) and the lowest level to send: everything, info and above (the default), warnings and errors, or errors only. Send test sends one line now and shows what the service answered.
#Your own endpoint
Each request has Content-Type: application/json, Content-Encoding: gzip and a body like:
[
{ "timestamp": "2026-10-26T10:00:01.234Z", "level": "error", "message": "GET /rest/v1/orders 500", "source": "api",
"project_ref": "<ref>", "method": "GET", "path": "/rest/v1/orders", "status": 500, "ms": 41, "ip": "203.0.113.7" }
]A batch has up to 1,000 lines. Answer with any 2xx status. The headers StitchDB-Timestamp and StitchDB-Signature: v1=<hex> let you check that it came from us: the signature is the HMAC-SHA256 of <timestamp>.<body> (the body after un-gzipping) with the drain's signing secret, which is shown once when you add the drain.
import { createHmac, timingSafeEqual } from 'node:crypto'
function verify(body, headers, secret) {
const want = 'v1=' + createHmac('sha256', secret).update(headers['stitchdb-timestamp'] + '.' + body).digest('hex')
const got = headers['stitchdb-signature'] || ''
const fresh = Math.abs(Date.now() / 1000 - Number(headers['stitchdb-timestamp'])) < 300
return fresh && got.length === want.length && timingSafeEqual(Buffer.from(got), Buffer.from(want))
}#When the service is down
Lines wait on our side and are retried with growing delays (10 seconds up to 30 minutes), oldest first, so nothing is lost during a short outage. Up to 3 days or 256 MB of lines wait per drain; older lines are dropped and counted on the drain. When sending has failed for an hour, the project owner gets one e-mail. The dashboard shows each drain's status, lines sent, lines waiting and the last error.
#From the API
curl -X POST "https://stitchdb.com/platform/v1/projects/<ref>/log-drains" \
-H "Authorization: Bearer $STITCHDB_TOKEN" -H "Content-Type: application/json" \
-d '{ "name": "Datadog", "kind": "datadog", "config": { "site": "datadoghq.eu", "api_key": "<key>" }, "sources": ["api", "postgres"], "min_level": "warn" }'| Request | What it does |
|---|---|
GET /projects/<ref>/log-drains | The drains with status, counts and last error (never the keys) |
POST /projects/<ref>/log-drains | Add a drain: name, kind (datadog, loki, http), config, sources, min_level |
PATCH /projects/<ref>/log-drains/<id> | Change it, or { "enabled": false } to turn it off. Keys left out are kept. |
POST /projects/<ref>/log-drains/<id>/test | Send one test line |
DELETE /projects/<ref>/log-drains/<id> | Delete it and the lines waiting for it |