Sign in
As a customer, with dashboard credentials.
As an operator, with the admin key.
Your issuer: –
(customer id –)
Put the issuer in the iss claim of every grant token you sign.
Recent usage
| When | Session | Chunk | Model | Seconds | Characters | Node |
|---|
Signing keys
You sign end-user grant tokens with the private half. We only store the public half,
so we can check your tokens but never mint one. Each token carries
iss = – and the key id below as kid.
| Key id | Public key | Status | Created |
|---|
Registering your own key is the safer route: generate the pair on your side and we never hold the private half at all. Existing keys keep working until you retire them, so rotating never breaks a live session.
API keys
| Name | Prefix | Scopes | Last used | Status |
|---|
Least privilege by default. keys:write also permits registering and
retiring signing keys, so grant it only to a key that needs to — and never to
one that lives in a browser.
Webhook
When one of your file jobs ends we POST to this address, so a server that cannot keep an event stream open still hears. It carries the job id and how it ended, never the transcript: fetch that with the job's token. One address per account, public HTTPS only, on a domain you have verified below.
1. Verify your domain
Add the domain your webhook lives on, then create the TXT record we show in its DNS.
Names under it are covered too: verifying example.com allows
https://hooks.example.com/….
| Domain | Status | TXT record to create |
|---|
2. Your webhook
Deliveries
| When | Event | Status | Tries | Last answer |
|---|
How to receive them
1. Prove the domain is yours. Create the TXT record shown above, at
_vesper-challenge.<your domain>, and press Verify. DNS changes can take
a few minutes to show. Keep the record: removing the domain here stops deliveries.
2. Check every delivery is ours. Each carries
Vesper-Signature: t=<unix seconds>,v1=<hex>, where v1 is
HMAC-SHA256 of <t>.<raw body> with your secret. Compare in constant time,
and refuse a t more than five minutes old.
const [t, v1] = header.split(',').map(p => p.split('=')[1]);
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
const ok = fresh && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
3. Answer quickly with any 2xx. Anything else is tried again, with growing waits,
for about a day. The same event can arrive twice: its id tells you.
Events: job.done, job.failed and webhook.test, as
{"id", "type", "created", "data": {"job_id", "status", "audio_seconds", "error"}}.
Credit ledger
Every change to your balance is a line here — the balance is the sum, never an edited number.
| When | Reason | Reference | Seconds |
|---|
GPU servers
live| Server | State | Node | Jobs now | Started | Stops |
|---|
Add a GPU server by hand
Seldom needed: a server lists itself here the first time its node calls with a key of its
own (made under "Node keys" below), once UpCloud confirms the server and that the call came
from it. By hand, the server's UUID is on its page at UpCloud, and the node id is the
NODE_ID its node runs with. Either way the token must have permission for the server.
Nodes and models
liveA node picks up a new model at once. It loads the new one beside the old, which keeps serving until it is ready, so live sessions are not interrupted.
| Node | Status | Jobs now | Running | Runs on | Model |
|---|
Node keys
A node on another server is let in by a key of its own, which also says which node it is. No address is listed anywhere. Revoke a key and its node is cut off at once. The node on this server uses the shared key, which is only good on our own network.
| Node | Key | Created | Last used | Status |
|---|
Customers
| Customer | Slug | Plan | Status | Used (s) | Balance (s) | Sessions |
|---|