> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tell.rs/llms.txt
> Use this file to discover all available pages before exploring further.

# HTTP Source

> HTTP ingestion for the JavaScript SDK and web apps — JSONL and binary support with TLS, CORS, and rate limiting.

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.

```toml theme={null}
[sources.http]
port = 8080
```

## Endpoints

| Method | Path         | Content-Type                | Description                |
| ------ | ------------ | --------------------------- | -------------------------- |
| POST   | `/v1/events` | `application/json`          | Ingest events (JSONL)      |
| POST   | `/v1/logs`   | `application/json`          | Ingest logs (JSONL)        |
| POST   | `/v1/ingest` | `application/x-flatbuffers` | Binary FlatBuffer batches  |
| GET    | `/health`    | —                           | Returns `{"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](/pipeline/sources/tcp) 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.

```bash theme={null}
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](/running-tell/api-keys) for creating and managing them.

## Responses

**202 Accepted** — all items ingested successfully:

```json theme={null}
{"accepted": 12, "request_id": "req_a1b2c3"}
```

**207 Multi-Status** — partial success (some items rejected):

```json theme={null}
{
  "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:

```toml theme={null}
[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:

```toml theme={null}
[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](#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`:

```toml theme={null}
[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

```toml theme={null}
[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](/pipeline/sources/tcp) — higher throughput for native SDKs
* [JavaScript SDK](/tracking/sdks/javascript) — the primary client for the HTTP source
* [API keys](/running-tell/api-keys) — create and manage streaming keys
* [Routing](/pipeline/routing) — control where HTTP data goes after ingestion
