Skip to content
skyl

Gateway configuration

Every SKYL_* variable, with defaults and validation rules.

Everything is an environment variable. There are no flags and no config file — which makes the gateway trivially deployable in a container and keeps its whole configuration surface visible in one place.

Server#

VariableDefaultDescription
SKYL_ADDR:8080The listen address.
SKYL_AUTH_TOKENRequiredThe bearer token callers must present. There is no flag to disable authentication: the server refuses to start without this, because an accidental open relay to billed endpoints must not be one environment variable away.
SKYL_DEFAULT_PROVIDERthe alphabetically first registered providerThe provider used when a request omits one. Naming a provider that is not registered is a startup error, not a runtime surprise.
SKYL_REQUEST_TIMEOUT120sBounds a single upstream request. Applied inside each handler rather than at the router, so a streaming response is not severed mid-generation.
SKYL_INCLUDE_RAWfalseIncludes the provider's untouched response body in the `raw` field. Off by default: raw bodies can echo request content.

Providers#

VariableDefaultDescription
ANTHROPIC_API_KEYRegisters the anthropic provider when present.
OPENAI_API_KEYRegisters the openai provider when present.
GEMINI_API_KEYRegisters the gemini provider when present.
SKYL_COMPAT_BASE_URLRegisters an OpenAI-compatible provider pointed at this host. This is the variable that triggers registration; the other two refine it.
SKYL_COMPAT_NAMEcompatThe name the compatible provider is registered under.
SKYL_COMPAT_API_KEYThe credential for the compatible host. Omit it for Ollama, vLLM or LM Studio.

A provider is registered for each key present, so an operator controls the provider set purely through the environment. SKYL_COMPAT_BASE_URL is the variable that triggers registration of a compatible host; the other two refine it.

Client behaviour#

These are passed through to skyl.Option on every provider client, so an operator can see and change them — previously they were fixed at their defaults with no way to do either.

VariableDefaultDescription
SKYL_MAX_RETRIES3Passed through to skyl.WithMaxRetries on every provider client.
SKYL_RETRY_BASE_DELAY500msThe base of the exponential backoff, via skyl.WithRetryDelay.
SKYL_RETRY_MAX_DELAY30sThe ceiling on skyl's computed backoff, via skyl.WithRetryDelay.
SKYL_RETRY_AFTER_CAP5mBounds how long a provider's own Retry-After hint may delay a retry. Separate from the backoff ceiling, because a provider asking for 60 seconds is a normal rate-limit window while one asking for an hour should not wedge the caller.
SKYL_ATTEMPT_TIMEOUT10mBounds a single attempt, via skyl.WithTimeout. Per attempt, not per call — the retry sequence as a whole is bounded by SKYL_REQUEST_TIMEOUT.

Hardening#

VariableDefaultDescription
SKYL_AUTH_TOKENSAdditional labelled tokens, as "label:token,label:token". The label appears in logs and metrics; the token never does. This is what makes rotation possible without downtime.
SKYL_MAX_CONCURRENTunlimitedCaps in-flight requests using chi’s Throttle middleware. Set it: an unbounded gateway converts a traffic spike into a provider rate-limit incident.
SKYL_ALLOWED_ORIGINSCORS disabledA comma-separated allow-list of browser origins. Leave it unset unless a browser calls the gateway directly — which means shipping a token to the browser, so think first.
SKYL_HEARTBEAT_INTERVAL15sHow often an SSE keep-alive frame is written on an idle stream, so proxies do not time the connection out mid-generation.

SKYL_AUTH_TOKENS takes label:token,label:token. The label appears in logs and metrics; the token never does. That is what makes rotation possible without downtime — add a new token, migrate callers, remove the old one.

Observability#

VariableDefaultDescription
SKYL_METRICSfalseEnables /metrics and installs the telemetry hook on every provider client, so the metrics cover all providers rather than whichever one happened to be wired first.

Enabling metrics installs the telemetry hook on every provider client, so the metrics cover all providers rather than whichever one happened to be wired first.

Validation#

Every parse error names its variable:

Output
SKYL_REQUEST_TIMEOUT: time: invalid duration "12x"

Without that, an operator sees invalid duration "12x" with nothing to say which of a dozen settings is wrong, on a process that has already refused to start.

Usage#

A production configuration

SKYL_ADDR=:8080
SKYL_AUTH_TOKEN=<32 random bytes, hex>
SKYL_AUTH_TOKENS=rotating:<second token>
SKYL_MAX_CONCURRENT=64
SKYL_REQUEST_TIMEOUT=120s
SKYL_ATTEMPT_TIMEOUT=60s
SKYL_MAX_RETRIES=3
SKYL_METRICS=true

ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
SKYL_DEFAULT_PROVIDER=anthropic
SKYL_ADDR=:8080
SKYL_AUTH_TOKEN=<32 random bytes, hex>
SKYL_AUTH_TOKENS=rotating:<second token>
SKYL_MAX_CONCURRENT=64
SKYL_REQUEST_TIMEOUT=120s
SKYL_ATTEMPT_TIMEOUT=60s
SKYL_MAX_RETRIES=3
SKYL_METRICS=true

ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
SKYL_DEFAULT_PROVIDER=anthropic

Local development against the sandbox

SKYL_AUTH_TOKEN=local-dev-token
SKYL_COMPAT_BASE_URL=http://127.0.0.1:8099/compat/v1
SKYL_COMPAT_API_KEY=sandbox-key
SKYL_COMPAT_NAME=sandbox
SKYL_INCLUDE_RAW=true
SKYL_AUTH_TOKEN=local-dev-token
SKYL_COMPAT_BASE_URL=http://127.0.0.1:8099/compat/v1
SKYL_COMPAT_API_KEY=sandbox-key
SKYL_COMPAT_NAME=sandbox
SKYL_INCLUDE_RAW=true

Troubleshooting#

The process refuses to start and names a variable

A parse error. The message includes the variable name and the underlying reason — that naming is deliberate, because a bare parse error on startup is otherwise unactionable.

Retries seem to ignore my settings

SKYL_MAX_RETRIES and friends apply to the upstream client. They are bounded by SKYL_REQUEST_TIMEOUT, so a short request timeout will cut a retry sequence short regardless of the retry count.

raw is missing from responses

SKYL_INCLUDE_RAW defaults to false. Unlike the library, where Raw is always populated, the gateway omits it by default because the body crosses a network boundary and can echo request content back to a caller who should not see it.

Edit this page on GitHub