Developer guide
Authentication and accounts
Every call carries a short-lived token that says two things: which workspace you are (api_key) and which of your end users this is (account_key). The account is how usage is attributed and limited, so use a stable id per end user — not one shared value. You set both once, on the client (createClient, the React provider, or the Angular provideSpeechineer); every session starts from it.
Two modes
Each API key is Unsigned or Signed; the key you use decides which of the two below applies. You set this per key in your workspace.
| Mode | You pass | Use for |
|---|---|---|
| Unsigned | apiKey + account | Development. The key sits in your frontend, so anyone who reads it can spend your units. |
| Signed | token | Production. Your backend signs each token, so only software you control can start a workflow. |
Unsigned
// Unsigned: the SDK builds the token from the key and the account.
import { createClient } from "@speechineer/js";
const speechineer = createClient({
apiKey: import.meta.env.VITE_SPEECHINEER_API_KEY,
account: { key: currentUser.id },
});
// React: <SpeechineerProvider apiKey={…} account={{ key: currentUser.id }}>
// Angular: provideSpeechineer({ apiKey: …, account: { key: currentUser.id } })Signed
Mint the token in your backend with the private key of a signing key you created in your workspace, then give the client a function that fetches it — a fresh token per session start, so expiry is never your problem. Claims: api_key, account_key, optional account_pseudonym, plus aud: "speechineer", iat, exp, and a unique jti. Keep the lifetime short — minutes, not hours.
import { SignJWT, importPKCS8 } from "jose";
const pk = await importPKCS8(process.env.SPEECHINEER_SIGNING_KEY!, "PS256");
// Your backend. Return this to the browser; it is short-lived by design.
export async function mintSpeechineerToken(apiKey: string, accountKey: string) {
return await new SignJWT({ api_key: apiKey, account_key: accountKey })
.setProtectedHeader({ alg: "PS256", kid: process.env.SPEECHINEER_SIGNING_KID!, typ: "JWT" })
.setAudience("speechineer")
.setIssuedAt()
.setExpirationTime("5m")
.setJti(crypto.randomUUID())
.sign(pk);
}// Signed: hand the client a token provider; the SDK calls it for every session it
// starts or resumes and forwards the token untouched.
import { createClient } from "@speechineer/js";
const speechineer = createClient({
token: () => fetch("/api/speechineer-token").then((res) => res.text()),
});
// React: <SpeechineerProvider token={fetchSpeechineerToken}>
// Angular: provideSpeechineer({ token: fetchSpeechineerToken })Rotating a signing key
Rotating creates the replacement immediately and keeps the previous key valid until the deadline you choose (default 7 days, at most 30). Deploy the new private key before that date: tokens signed with the old key id keep working until then, and stop afterwards.
Managing accounts from your backend
Accounts appear on first use, so you do not have to create them. Use this only when you want to set a pseudonym or a per-account limit up front. The request carries the same signed token; the account is named in the path.
PUT /api/v1/accounts/account_acme_003/ HTTP/1.1
Host: api.speechineer.com
Authorization: Bearer <the same signed token>
Content-Type: application/json
{
"account_pseudonym": "Order desk EU",
"quotas": [
{ "units_limit": "1000.0", "period": "month", "sync": "subscription" }
]
}GET reads the account and its usage this cycle, PATCH updates the pseudonym or limits, and DELETE archives it — usage history is kept.