Skip to content

Commit f28d04a

Browse files
committed
docs(synapse): fix config schemas, add guardrails page, correct health responses
Config examples now match actual Rust structs: rate limiting under [server.rate_limit], telemetry using [telemetry.exporter] with protocol field, auth via api_url+gateway_secret (not static keys/JWT), routing with correct field names. Added NVIDIA provider, guardrails doc page, and fixed health endpoint response formats.
1 parent ec565be commit f28d04a

9 files changed

Lines changed: 461 additions & 158 deletions

File tree

content/docs/grid/synapse/api.mdx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,13 +328,17 @@ Response:
328328

329329
## GET /health
330330

331-
Health check endpoint. No authentication required.
331+
Health check endpoint. No authentication required. The gateway binary listens on port 3000 by default; Docker Compose maps host port 6000 to container port 3000.
332332

333333
```bash
334+
# Docker Compose (host port)
334335
curl http://localhost:6000/health
336+
337+
# Binary (default port)
338+
curl http://localhost:3000/health
335339
```
336340

337-
Response:
341+
Response (plain text):
338342

339343
```
340344
ok

content/docs/grid/synapse/authentication.mdx

Lines changed: 37 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Synapse supports multiple authentication methods to secure access to its API end
77

88
## API Key Authentication
99

10-
The default authentication method. Clients pass an API key in the request header, and Synapse validates it against the configured key store.
10+
The default authentication method. Clients pass an API key in the request header, and Synapse validates it by calling the synapse-api service's `/internal/resolve-key` endpoint.
1111

1212
### Header Format
1313

@@ -25,53 +25,46 @@ curl -H "x-api-key: your-api-key" \
2525

2626
```toml
2727
[auth]
28-
method = "api_key"
29-
30-
# Static key list
31-
[[auth.api_keys]]
32-
key = "{{ env.SYNAPSE_API_KEY }}"
33-
name = "production"
28+
enabled = true
29+
api_url = "http://synapse-api:4000"
30+
gateway_secret = "{{ env.GATEWAY_SECRET }}"
31+
cache_ttl_seconds = 30
32+
cache_capacity = 10000
33+
public_paths = ["/health"]
3434
```
3535

36+
| Field | Description | Default |
37+
|-------|-------------|---------|
38+
| `enabled` | Enable API key auth | false |
39+
| `api_url` | URL of the synapse-api service | -- (required) |
40+
| `gateway_secret` | Shared secret for gateway-to-API authentication | -- (required) |
41+
| `cache_ttl_seconds` | How long to cache resolved API keys | 30 |
42+
| `cache_capacity` | Maximum number of cached key resolutions | 10000 |
43+
| `public_paths` | Paths that skip authentication | `["/health"]` |
44+
| `tls_skip_verify` | Skip TLS verification for API calls (dev only) | false |
45+
3646
<Callout type="warning">
37-
Always use environment variable interpolation for API keys. Never hardcode secrets in config files.
47+
Always use environment variable interpolation for the gateway secret. Never hardcode secrets in config files.
3848
</Callout>
3949

40-
### Key Validation
50+
### How It Works
4151

42-
When Synapse runs alongside the Omni API (synapse-api), keys are validated against the API's key store. This enables per-key rate limits, usage tracking, and key rotation without restarting Synapse.
52+
When a request arrives with an API key, Synapse calls synapse-api's `/internal/resolve-key` endpoint (authenticated with `gateway_secret`) to validate the key and resolve the associated client identity. Resolved keys are cached locally for `cache_ttl_seconds` to avoid per-request API calls.
4353

44-
## OAuth2 / JWT
54+
The synapse-api can push cache invalidations to the gateway via the `/internal/invalidate-key` endpoint when keys are rotated or revoked.
4555

46-
For applications that use OAuth2 or JWT-based authentication, Synapse can validate tokens against a JWKS endpoint.
56+
### BYOK Vault Integration
4757

48-
### Configuration
58+
When configured, Synapse resolves BYOK (Bring Your Own Key) provider keys from a Gatekeeper vault instead of the synapse-api database:
4959

5060
```toml
51-
[auth]
52-
method = "jwt"
53-
54-
[auth.jwt]
55-
jwks_url = "https://auth.omni.dev/.well-known/jwks.json"
56-
issuer = "https://auth.omni.dev"
57-
audience = "synapse"
61+
[auth.vault]
62+
url = "https://gatekeeper.omni.dev"
63+
service_key = "{{ env.GATEKEEPER_SERVICE_KEY }}"
64+
cache_ttl_seconds = 300
65+
cache_capacity = 10000
5866
```
5967

60-
| Field | Description | Required |
61-
|-------|-------------|----------|
62-
| `jwks_url` | URL to the JWKS endpoint for public key discovery | Yes |
63-
| `issuer` | Expected `iss` claim in the JWT | Yes |
64-
| `audience` | Expected `aud` claim in the JWT | No |
65-
66-
### Token Usage
67-
68-
```bash
69-
curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
70-
http://localhost:6000/v1/chat/completions ...
71-
```
72-
73-
Synapse caches the JWKS response and refreshes it periodically. Tokens are validated for signature, expiry, issuer, and audience.
74-
7568
## CSRF Protection
7669

7770
Synapse includes CSRF protection for browser-based clients.
@@ -81,10 +74,10 @@ Synapse includes CSRF protection for browser-based clients.
8174
```toml
8275
[server.csrf]
8376
enabled = true
84-
token_header = "X-CSRF-Token"
77+
header_name = "X-Synapse-CSRF-Protection"
8578
```
8679

87-
When enabled, non-GET requests from browser clients must include a valid CSRF token in the configured header. Tokens are issued via a dedicated endpoint or embedded in the initial page load.
80+
When enabled, non-GET requests from browser clients must include a valid CSRF token in the configured header.
8881

8982
<Callout type="info">
9083
CSRF protection is primarily relevant when Synapse is accessed directly from a browser. API clients using `Authorization` headers are not affected.
@@ -98,11 +91,13 @@ Synapse tracks which client is making each request for usage analytics and rate
9891
2. **JWT claims** -- `sub` or `client_id` claim
9992
3. **IP address** -- fallback when no other identifier is available
10093

101-
### Custom Client Header
94+
### Custom Client Identification
95+
96+
Client identification is configured separately under `[server.client_identification]`:
10297

10398
```toml
104-
[auth]
105-
client_id_header = "X-Client-Id"
99+
[server.client_identification]
100+
header_name = "X-Client-Id"
106101
```
107102

108103
When set, Synapse uses the value of this header as the client identifier, regardless of the authentication method.
@@ -119,14 +114,9 @@ When set, Synapse uses the value of this header as the client identifier, regard
119114

120115
## Unauthenticated Endpoints
121116

122-
The following endpoints do not require authentication:
123-
124-
- `GET /health` -- health check
125-
- `GET /v1/models` -- model list (configurable, can require auth)
126-
127-
To require authentication on all endpoints:
117+
Paths listed in `public_paths` skip authentication. By default, only `/health` is public:
128118

129119
```toml
130120
[auth]
131-
require_auth_for_models = true
121+
public_paths = ["/health"]
132122
```

content/docs/grid/synapse/configuration.mdx

Lines changed: 91 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ Synapse is configured via a TOML file passed with `--config`. This page covers e
99

1010
```toml
1111
[server]
12-
listen_address = "0.0.0.0:6000"
12+
listen_address = "0.0.0.0:3000"
1313
```
1414

15+
The binary listens on port 3000 by default. In Docker Compose, port 6000 on the host is mapped to 3000 inside the container.
16+
1517
### TLS
1618

1719
```toml
@@ -22,7 +24,13 @@ key_path = "/etc/synapse/key.pem"
2224

2325
### Health Endpoint
2426

25-
The health endpoint is always available at `GET /health` and returns `ok` (plain text) when the server is ready.
27+
The health endpoint is enabled by default at `GET /health` and returns `ok` (plain text) when the server is ready. The path and enabled state are configurable:
28+
29+
```toml
30+
[server.health]
31+
enabled = true
32+
path = "/health"
33+
```
2634

2735
### CORS
2836

@@ -39,7 +47,7 @@ max_age = 3600
3947
```toml
4048
[server.csrf]
4149
enabled = true
42-
token_header = "X-CSRF-Token"
50+
header_name = "X-Synapse-CSRF-Protection"
4351
```
4452

4553
## Environment Variable Interpolation
@@ -69,28 +77,68 @@ See [synapse.omni.dev/pricing](https://synapse.omni.dev/pricing) for current pla
6977

7078
## Rate Limiting
7179

72-
### In-memory
80+
Rate limiting lives under `[server.rate_limit]`. It supports global and per-IP request limits with either in-memory or cache-backed (distributed) storage.
81+
82+
### In-memory (default)
7383

7484
```toml
75-
[rate_limit]
76-
backend = "memory"
77-
requests_per_minute = 60
78-
burst = 10
85+
[server.rate_limit]
86+
87+
[server.rate_limit.storage]
88+
type = "memory"
89+
90+
[server.rate_limit.global]
91+
requests = 60
92+
window = "1m"
7993
```
8094

81-
### Redis
95+
### Cache-backed (distributed)
8296

8397
```toml
84-
[rate_limit]
85-
backend = "redis"
86-
redis_url = "{{ env.REDIS_URL }}"
87-
requests_per_minute = 120
88-
burst = 20
89-
key_prefix = "synapse:rl:"
98+
[server.rate_limit]
99+
100+
[server.rate_limit.storage]
101+
type = "cache"
102+
url = "{{ env.VALKEY_URL }}"
103+
pool_size = 10
104+
connect_timeout = 5
105+
106+
[server.rate_limit.global]
107+
requests = 120
108+
window = "1m"
109+
110+
[server.rate_limit.per_ip]
111+
requests = 30
112+
window = "1m"
90113
```
91114

115+
### Token-based rate limits
116+
117+
For LLM token budgets, configure `[server.rate_limit.tokens]`:
118+
119+
```toml
120+
[server.rate_limit.tokens.default]
121+
tokens = 100000
122+
window = "1h"
123+
124+
[server.rate_limit.tokens.groups.premium]
125+
tokens = 1000000
126+
window = "1h"
127+
```
128+
129+
| Field | Description | Default |
130+
|-------|-------------|---------|
131+
| `storage.type` | `memory` or `cache` | `memory` |
132+
| `storage.url` | Cache connection URL (required for `cache` type) | -- |
133+
| `storage.pool_size` | Connection pool size | 10 |
134+
| `storage.connect_timeout` | Connection timeout in seconds | 5 |
135+
| `global.requests` | Max requests per window (all clients) | -- |
136+
| `global.window` | Window duration (e.g. `"1m"`, `"1h"`) | -- |
137+
| `per_ip.requests` | Max requests per IP per window | -- |
138+
| `per_ip.window` | Window duration for per-IP limit | -- |
139+
92140
<Callout type="info">
93-
Redis-backed rate limiting is recommended for multi-instance deployments where limits should be shared across replicas.
141+
Cache-backed rate limiting is recommended for multi-instance deployments where limits should be shared across replicas.
94142
</Callout>
95143

96144
## Failover
@@ -124,21 +172,21 @@ recovery_seconds = 30
124172

125173
## Logging
126174

127-
```toml
128-
[logging]
129-
level = "info"
130-
format = "json"
175+
Log level is controlled by the `RUST_LOG` environment variable:
176+
177+
```bash
178+
RUST_LOG=synapse=info,tower_http=info
131179
```
132180

133-
Supported levels: `trace`, `debug`, `info`, `warn`, `error`. The `json` format is recommended for production; use `pretty` for local development.
181+
Supported levels: `trace`, `debug`, `info`, `warn`, `error`.
134182

135183
## Production Example
136184

137185
A full production config combining multiple sections:
138186

139187
```toml
140188
[server]
141-
listen_address = "0.0.0.0:6000"
189+
listen_address = "0.0.0.0:3000"
142190

143191
[server.tls]
144192
cert_path = "/etc/synapse/cert.pem"
@@ -150,11 +198,22 @@ allowed_origins = ["https://app.omni.dev"]
150198
[server.csrf]
151199
enabled = true
152200

153-
[rate_limit]
154-
backend = "redis"
155-
redis_url = "{{ env.REDIS_URL }}"
156-
requests_per_minute = 120
157-
burst = 20
201+
[server.rate_limit]
202+
[server.rate_limit.storage]
203+
type = "cache"
204+
url = "{{ env.VALKEY_URL }}"
205+
206+
[server.rate_limit.global]
207+
requests = 120
208+
window = "1m"
209+
210+
[server.rate_limit.per_ip]
211+
requests = 30
212+
window = "1m"
213+
214+
[llm.failover]
215+
enabled = true
216+
max_attempts = 2
158217

159218
[llm.failover.circuit_breaker]
160219
error_threshold = 5
@@ -165,10 +224,6 @@ recovery_seconds = 30
165224
name = "frontier"
166225
models = ["openai/gpt-4o", "anthropic/claude-sonnet-4-20250514"]
167226

168-
[logging]
169-
level = "info"
170-
format = "json"
171-
172227
[llm.providers.anthropic]
173228
type = "anthropic"
174229
api_key = "{{ env.ANTHROPIC_API_KEY }}"
@@ -177,6 +232,11 @@ api_key = "{{ env.ANTHROPIC_API_KEY }}"
177232
type = "openai"
178233
api_key = "{{ env.OPENAI_API_KEY }}"
179234

235+
[auth]
236+
enabled = true
237+
api_url = "http://synapse-api:4000"
238+
gateway_secret = "{{ env.GATEWAY_SECRET }}"
239+
180240
[stt.providers.whisper]
181241
type = "whisper"
182242
api_key = "{{ env.OPENAI_API_KEY }}"

0 commit comments

Comments
 (0)