SpeechRelay Docs

Authentication

The bearer credential every request carries: issued keys, scopes, expiry, revocation, and the browser WebSocket seam.

The credential

The API accepts a bearer credential in the Authorization header. The server compares the whole header in constant time. A missing or wrong credential is 401.

Authorization: Bearer <token>

Issued keys

Tokens have the fixed form srk_<16>_<32>: a 16-character key id and a 32-character secret, both drawn from the alphabet abcdefghijklmnopqrstuvwxyz234567.

srk_aaaaaaaaaaaaaaaa_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

The plaintext token is shown exactly once at issuance and is never recoverable. If it is lost, issue a new one and revoke the old. The server stores only a salted SHA-256 verifier.

Scopes

A key carries scopes. An empty scope set means unrestricted; that is the deliberate default, so keys issued before scopes existed keep working. The two scopes in use:

A valid credential presented for an operation outside its scopes is 403.

401 versus 403

401  we do not accept this credential      missing, wrong or expired
403  we accept it but it may not do this   valid, but outside its scopes

A credential that is missing, wrong or expired is 401. The same status meets a key whose expiry has passed. A valid credential that asks for an operation its scopes do not cover is 403.

Expiry

A key may carry an expiry. Expiry is checked on every verification against a fresh clock, so an expired key stops working within the verification cache window rather than at some cached moment. An expired key is 401.

Revocation

Revocation is not instant. A revoked key stops working within the verification cache TTL plus storage propagation, not at the moment it is revoked.

Browser credentials

A browser cannot set an Authorization header on a WebSocket. Both WebSocket routes therefore also accept the credential inside the Sec-WebSocket-Protocol header, as bearer.<token> alongside the required dicon.v1:

const socket = new WebSocket(url, ["dicon.v1", `bearer.${token}`]);

When both forms are present, the Authorization header wins. The server selects the dicon.v1 subprotocol and never echoes the credential back.

The cost, stated plainly: this is a handshake request header. It is better than putting the token in a URL, but an intermediary that logs full request headers will capture it.

Handshake failures

A 401 on a WebSocket upgrade is a real HTTP response; the connection never opens. Your client should handle it as an authentication failure.