Authentication
XKOVA MPC does not use bearer tokens. You sign every request with an Ed25519 key. XKOVA stores only your public key, so it can verify your requests but can never produce them — there is no shared secret to leak from our side.
The model
Section titled “The model”- You hold an Ed25519 seed (32 bytes). You register its public key.
- For each request, you compute a stamp — a signature over the timestamp, a nonce, the HTTP method, the path, and a hash of the body.
- The stamp travels in the
X-Xkova-Stampheader. XKOVA verifies it against your registered public key.
Because the signature covers the method, path, and body, a captured request cannot be replayed against a different endpoint or with altered contents, and the timestamp and nonce bound its lifetime.
With the SDK
Section titled “With the SDK”The client stamps every call for you — you only supply the seed:
import { XkovaMpcClient } from "@xkova/mpc";
const xkova = new XkovaMpcClient({ baseUrl: "https://api.mpc-dev.xkova.com", seedHex: process.env.XKOVA_API_SEED!,});By hand
Section titled “By hand”If you are not on Node, build the stamp yourself. The SDK’s stamp() shows the
exact construction; the header is a base64url envelope of
{ pubkey, ts, nonce, sig }, where sig signs
ts ‖ nonce ‖ method ‖ path ‖ sha256(body).
import { keyPairFromSeed, stamp } from "@xkova/mpc";
const key = keyPairFromSeed(process.env.XKOVA_API_SEED!);const path = "/v1/vaults";const body = Buffer.from(JSON.stringify({ tier: "self", name: "treasury" }));
const res = await fetch("https://api.mpc-dev.xkova.com" + path, { method: "POST", headers: { "Content-Type": "application/json", "X-Xkova-Stamp": stamp(key, "POST", path, body), }, body,});The stamp signs the exact bytes you send, so serialize the body once and use
that same buffer for both the signature and the request. path includes /v1.
Minting a key
Section titled “Minting a key”Your first key is provisioned at onboarding. After that you can create more — one per service or environment, each with a label — so a leak is contained to one key you can revoke on its own:
import { generateKeyPair } from "@xkova/mpc";
const k = generateKeyPair();await xkova.createApiKey({ public_key: k.publicKey.toString("hex"), label: "ci-signer",});// Store k.seed securely — XKOVA only ever sees the public key.console.log("seed:", k.seed.toString("hex"));generateKeyPair runs entirely on your side; the seed never touches the network.
Listing and revoking
Section titled “Listing and revoking”const { api_keys } = await xkova.listApiKeys();await xkova.revokeApiKey("key_123");Revocation is immediate — the next request stamped with that key is rejected
401. Revoke a key the moment you suspect it is exposed; it does not affect any
vault or its funds, only the ability to sign requests.
Keep the seed safe
Section titled “Keep the seed safe”- The seed is a credential. Store it in a secrets manager, not in source.
- It authorizes API requests. It is not a key to any funds — moving funds is additionally gated by policy, limits, and (hosted tier) an end-user token. See How it works.
- Rotate by minting a new key, cutting over, then revoking the old one.