Networking & Auth
These failures happen before any inference work begins, which is good news: they are cheap to reproduce and the error messages are usually precise.
| Error code | Symptom & log signature | Root cause & first action |
|---|---|---|
ErrorERR_OCULIS_UPSTREAM_TLS_VERIFYHTTP 502 | Only self-hosted or on-prem upstreams fail; public providers work. x509: certificate signed by unknown authority | A private or intercepting CA is not present in the agent trust store. FixMount the CA bundle and set `tls.ca_bundle_path`. Never disable verification in production. Full procedure |
ErrorERR_OCULIS_AUTH_INVALID_KEYHTTP 401 | Every request from one client fails while others succeed. auth: key id=oc_live_3f2a… not found or revoked | Key revoked, rotated, or issued in a different environment than the one called. FixReissue the key and confirm the client targets the matching environment. Full procedure |
ErrorERR_OCULIS_AUTH_SCOPE_DENIEDHTTP 403 | Authentication succeeds but specific models or routes are refused. rbac: key lacks scope models:invoke for target gpt-4o | The API key’s scope set excludes the requested model or operation. FixGrant the scope on the key, or route the client to a permitted model alias. Full procedure |
WarningERR_OCULIS_BUDGET_EXCEEDEDHTTP 402 | A tenant is cut off mid-month while other tenants are unaffected. budget: tenant acme consumed 100.4% of monthly cap ($2,010/$2,000) | The tenant reached its configured spend ceiling. Working as designed. FixRaise the cap, or configure a downgrade route to a cheaper model at the ceiling. Full procedure |
WarningERR_OCULIS_RATE_LIMIT_LOCALHTTP 429 | Clients are throttled by Oculis itself rather than by the upstream provider. ratelimit: tenant acme exceeded 600 rpm (bucket=tenant:acme) | The tenant exceeded its configured request or token rate. FixRaise the limit, widen the burst allowance, or move the client to its own bucket. Full procedure |
No entry matches that filter. Try the error code alone, or search the whole site with ⌘K.
Isolating the failing hop
Section titled “Isolating the failing hop”There are four hops, and each has a different owner. Test them in order.
client ──1──► load balancer ──2──► Oculis ──3──► upstream provider │ └──4──► vector store / guardrail sidecar-
Client to load balancer.
Terminal window curl -sv https://gateway.example.com/healthz 2>&1 | grep -E '^[*<>]' | head -20 -
Load balancer to Oculis. From inside the network:
Terminal window curl -s http://oculis.internal:8080/healthz -
Oculis to upstream. Use the agent’s own view, which uses the same trust store and proxy settings as real traffic:
Terminal window oculis upstream test --name openai --verbose -
Oculis to auxiliary services.
Terminal window oculis vector test --store docsoculis guardrail test --policy pii-strict --input "ping"
TLS verification failures
Section titled “TLS verification failures”Signature: ERR_OCULIS_UPSTREAM_TLS_VERIFY
x509: certificate signed by unknown authorityPublic providers work; private or on-prem upstreams fail. A private CA — or a corporate TLS inspection proxy — is not in the agent’s trust store.
Diagnose
Section titled “Diagnose”openssl s_client -connect vllm.internal:8443 -showcerts </dev/null 2>/dev/null \ | openssl x509 -noout -issuer -subject -datesIf the issuer is your internal CA (or something named like a security appliance), that is the answer.
Resolve
Section titled “Resolve”oculis: tls: ca_bundle_path: /etc/ssl/certs/internal-ca.pem min_version: '1.2' # For mutual TLS to an upstream: client_cert_path: /etc/oculis/tls/client.crt client_key_path: /etc/oculis/tls/client.keytls: caBundle: configMapRef: name: internal-ca key: ca.pem minVersion: '1.2'
extraVolumeMounts: - name: internal-ca mountPath: /etc/ssl/certs/internal-ca.pem subPath: ca.pem readOnly: truedocker run -d --name oculis \ -v /etc/ssl/certs/internal-ca.pem:/etc/ssl/certs/internal-ca.pem:ro \ -e OCULIS_TLS_CA_BUNDLE=/etc/ssl/certs/internal-ca.pem \ ghcr.io/selaware/oculis-agent:1.4.0Expired certificates
Section titled “Expired certificates”x509: certificate has expired or is not yet valid: current time ... is after ...Also check clock skew. A node whose clock is days ahead will reject valid certificates:
timedatectl status | grep -E 'synchronized|Time zone'DNS and connectivity
Section titled “DNS and connectivity”dial tcp: lookup vllm.inference.svc.cluster.local: no such host# Resolutiongetent hosts vllm.inference.svc.cluster.local
# Reachability, without TLS in the waync -zv vllm.inference.svc.cluster.local 8000
# Kubernetes: does the Service have endpoints?kubectl get endpoints -n inference vllmAn empty ENDPOINTS column means no pods match the Service selector — the Service exists, but
points at nothing. That surfaces as a DNS or connection failure rather than as an obvious
misconfiguration.
Egress restrictions
Section titled “Egress restrictions”In a locked-down cluster, egress to cloud providers is frequently the missing piece:
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: oculis-egress namespace: oculisspec: podSelector: matchLabels: { app.kubernetes.io/name: oculis-agent } policyTypes: [Egress] egress: - to: - namespaceSelector: matchLabels: { kubernetes.io/metadata.name: inference } ports: - { protocol: TCP, port: 8000 } - ports: # DNS - { protocol: UDP, port: 53 } - { protocol: TCP, port: 53 } - ports: # HTTPS to cloud providers - { protocol: TCP, port: 443 }If you use an HTTP proxy for egress, tell the agent explicitly:
oculis: http_proxy: 'http://proxy.internal:3128' no_proxy: '.svc.cluster.local,10.0.0.0/8,localhost'Authentication failures
Section titled “Authentication failures”401 — identity
Section titled “401 — identity”Signature: ERR_OCULIS_AUTH_INVALID_KEY
auth: key id=oc_live_3f2a… not found or revokedThe key was not recognized. Check, in order:
oculis keys inspect --prefix oc_live_3f2a id key_01HQ8Z3K4M5N6P7Q8R9S tenant acme status REVOKED ← here it is revoked 2026-07-14T09:12:00Z reason="rotated" scopes models:invoke, models:listCommon causes:
- Revoked or rotated, and the client still holds the old value.
- Expired. Expiry returns
401, not403. - Wrong environment. A staging key against production is simply unknown.
- Header mangling. A proxy stripping or rewriting
Authorization.
# Confirm the header survives the full path:curl -s https://gateway.example.com/v1/debug/echo-headers \ -H "Authorization: Bearer $OCULIS_KEY" | grep -i authorization403 — permissions
Section titled “403 — permissions”Signature: ERR_OCULIS_AUTH_SCOPE_DENIED
rbac: key lacks scope models:invoke for target gpt-4oThe key is valid but not permitted. This is a different problem from 401 and needs a different
fix:
oculis keys test --id key_01HQ8Z3K4M5N6P7Q8R9S --scope models:invoke --route high-qualityGrant the scope or the route.
402 — budget
Section titled “402 — budget”A ERR_OCULIS_BUDGET_EXCEEDED is not an auth failure, even though it looks like
one. The key is valid and permitted; the tenant is out of budget. See
token and spend budgets.
Verification
Confirms every hop resolves, TLS validates against the configured trust store, and auth returns the right status for valid, invalid, and under-scoped keys.
1. All upstream connections are healthy from inside the pod.
oculis upstream test --all --verbose vllm-primary http://vllm.inference.svc:8000/v1 dns OK 10.4.2.11 tcp OK 3ms tls -- plaintext (in-cluster) http OK 200 (11ms) openai https://api.openai.com/v1 dns OK 104.18.7.192 tcp OK 14ms tls OK verified by DigiCert Global Root G2 (expires 2031-11-09) http OK 200 (152ms)[PASS] 2/2 upstreams reachable.2. Auth returns the correct status for each case.
oculis auth test --key "$OCULIS_KEY" --expect 200 && \oculis auth test --key "oc_live_invalid" --expect 401 && \oculis auth test --key "$OCULIS_KEY" --route high-quality --expect 403[PASS] valid key -> 200[PASS] unknown key -> 401 ERR_OCULIS_AUTH_INVALID_KEY[PASS] key without route -> 403 ERR_OCULIS_AUTH_SCOPE_DENIEDAll three must pass. A configuration that returns 200 for the third case is not enforcing route
restrictions, regardless of what the config file says.