Skip to main content
Web apps can’t easily connect to a custom TCP port, so the HTTP source provides an alternative ingestion path with JSONL and binary support. This is what the JavaScript SDK uses.
[sources.http]
port = 8080

Endpoints

MethodPathContent-TypeDescription
POST/v1/eventsapplication/jsonIngest events (JSONL)
POST/v1/logsapplication/jsonIngest logs (JSONL)
POST/v1/ingestapplication/x-flatbuffersBinary FlatBuffer batches
GET/healthReturns {"status": "ok"}
The JSONL endpoints accept one JSON object per line. The binary endpoint accepts pre-encoded FlatBuffer batches — the same wire format the TCP source uses. Content types application/octet-stream and application/x-tell are also accepted on the binary endpoint.

Authentication

Three ways to authenticate, in precedence order:
  1. Authorization: Bearer <key> header
  2. X-API-Key: <key> header
  3. ?token=<key> query parameter
The query parameter option exists for browser SDKs that need to avoid CORS preflight requests on simple POST requests.
curl -X POST https://tell.example.com/v1/events \
  -H "Authorization: Bearer a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" \
  -H "Content-Type: application/json" \
  -d '{"type":"track","event":"page_view","device_id":"abc123"}'
Keys are 32 hex characters. See API keys for creating and managing them.

Responses

202 Accepted — all items ingested successfully:
{"accepted": 12, "request_id": "req_a1b2c3"}
207 Multi-Status — partial success (some items rejected):
{
  "accepted": 10,
  "rejected": 2,
  "errors": [
    {"line": 3, "error": "missing required field: device_id"},
    {"line": 7, "error": "invalid event type"}
  ],
  "request_id": "req_a1b2c3"
}
400 Bad Request — all items rejected or invalid format. 401 Unauthorized — missing or invalid API key. 413 Payload Too Large — request body exceeds max_payload_size. 429 Too Many Requests — rate limited after repeated auth failures. 503 Service Unavailable — pipeline backpressure (channel full).

CORS and browser clients

CORS is enabled by default so browser SDKs work without extra setup. The source mirrors the request’s Origin header and allows Authorization, Content-Type, X-API-Key, and X-Workspace-ID headers. To restrict allowed origins:
[sources.http]
port = 8080
cors_origins = ["https://myapp.com", "https://staging.myapp.com"]

TLS

Tell supports HTTPS natively via rustls. Provide PEM-encoded certificate and key files:
[sources.http]
port = 443
tls_cert_path = "/etc/tell/cert.pem"
tls_key_path = "/etc/tell/key.pem"
When both paths are set, the source binds as HTTPS. Omit them to run plain HTTP (the default). For production, you can also terminate TLS at a reverse proxy like Caddy instead — see Reverse proxy below.

Reverse proxy

Running Caddy in front of Tell is the simplest way to get automatic HTTPS with Let’s Encrypt certificates and zero TLS configuration:
tell.example.com {
    reverse_proxy localhost:8080
}
Caddy handles certificate provisioning, renewal, and TLS termination automatically. Enable trust_proxy in Tell so the real client IP is captured from X-Forwarded-For:
[sources.http]
port = 8080
trust_proxy = true
This also works with nginx or any reverse proxy that sets X-Forwarded-For. Only enable trust_proxy when the HTTP source is behind a trusted proxy — clients can forge the header otherwise.

Rate limiting

Authentication failures are rate-limited per IP. After 10 failures within 60 seconds, the IP is temporarily blocked with 429 Too Many Requests. The rate limiter cleans up expired entries every 5 minutes.

Safety limits

JSONL endpoints enforce two limits to prevent denial-of-service:
  • 10,000 lines per request — requests with more lines are rejected
  • 32 levels of JSON nesting — deeply nested objects are rejected before parsing
These limits are not configurable. Malformed JSON lines are skipped individually — a single bad line won’t reject the entire request (you’ll get a 207 response with per-line errors).

Configuration reference

[sources.http]
port = 8080
address = "::"              # Bind address (dual-stack IPv4+IPv6)
max_payload_size = 16777216  # Max request body in bytes (16 MB)
batch_size = 500             # Items per internal batch
flush_interval = "100ms"     # Flush partial batches after this interval
request_timeout = "30s"      # HTTP request timeout
cors_enabled = true          # Enable CORS headers
cors_origins = []            # Allowed origins (empty = all)
request_logging = false      # Log every HTTP request
trust_proxy = false          # Trust X-Forwarded-For header
tls_cert_path = ""           # PEM certificate path (enables HTTPS)
tls_key_path = ""            # PEM private key path (enables HTTPS)

What’s next

  • TCP source — higher throughput for native SDKs
  • JavaScript SDK — the primary client for the HTTP source
  • API keys — create and manage streaming keys
  • Routing — control where HTTP data goes after ingestion