UNAUTHENTICATED — missing x-api-key
`Status { code: Unauthenticated }` on a Yellowstone gRPC subscribe means the server didn't see a valid API key in the metadata. The single most common cause is attaching the key to the client's constructor but not to the individual streaming call.
Root causes
Ranked by frequency. First cause is the one to check first.
- 01API key passed at channel creation but not as request metadata — most providers require per-call metadata.
- 02Using `Authorization: Bearer` when the provider expects `x-api-key`, or vice versa.
- 03API key has leading/trailing whitespace from copy-paste (Subglow trims on server; some providers reject silently).
- 04API key was rotated in the dashboard but the deploy still uses the old one.
- 05Using a free-trial key that expired — trials auto-expire after the 14-day window.
Fix steps
- 1
Attach the key as request metadata, not channel options
In TypeScript: `await client.subscribe({ 'x-api-key': KEY })`. In Python: `stub.Subscribe(req, metadata=(('x-api-key', KEY),))`. In Rust: add `.add_metadata('x-api-key', KEY)` to the interceptor.
- 2
Use the exact header name the provider expects
Subglow and Chainstack use `x-api-key`. Triton One uses `authorization: Bearer <token>`. Helius Laserstream uses the key in the URL path, not as metadata. Mismatched headers always yield UNAUTHENTICATED.
- 3
Trim whitespace
`const key = process.env.SUBGLOW_API_KEY?.trim()`. A single newline from your secret manager silently breaks gRPC metadata.
- 4
Verify the key directly
Run `grpcurl -H 'x-api-key: YOUR_KEY' grpc.subglow.io:443 geyser.Geyser/GetVersion`. A successful GetVersion response confirms the key is valid and attached correctly. If that fails too, regenerate the key in your Subglow dashboard.
Code example
use yellowstone_grpc_client::GeyserGrpcClient;
use tonic::transport::ClientTlsConfig;
let tls_config = ClientTlsConfig::new().with_native_roots();
let mut client = GeyserGrpcClient::build_from_shared("https://grpc.subglow.io:443")?
.x_token(Some("YOUR_API_KEY"))?
.tls_config(tls_config)?
.connect()
.await?;
// x-api-key is automatically attached to every call by the x_token interceptor.
let (mut subscribe_tx, mut stream) = client.subscribe().await?;Related errors
- UNAVAILABLE: connection refusedYour gRPC client got `Status { code: Unavailable }` with `connection refused` (or `transport is closing`). The TCP handshake never completed — either you're hitting the wrong port, TLS is misconfigured, or the endpoint is genuinely down.
- Free trial expired — API key invalidYour free-trial API key was valid for 14 days and has expired. Streams will terminate with UNAUTHENTICATED and JSON-RPC calls will return 401. Upgrade to Sniper ($99/mo) or Pro ($249/mo) to keep the same key active.
- TLS handshake failed on grpc.subglow.ioThe TCP socket opened but TLS negotiation failed. Usually a stale root CA bundle inside a Docker image, a missing SNI extension, or a corporate MITM proxy intercepting and presenting its own certificate.
Want an endpoint that just works?
Subglow is flat-priced Solana gRPC + JSON-RPC on a single API key. Pre-parsed JSON, dedicated sendTransaction bucket, 99.9% latency SLA on Dedicated. No credit juggling, no surprise bills.