Embed tokens
Minting the five-minute token the Manage API keys component uses.
packages/api/src/embed/embed-tokens.controller.ts by packages/docs/tools/generate-reference.mjs. Do not edit by hand: src/reference.spec.ts regenerates it and fails on a difference.Report section 7.2, step 1: the vendor's backend exchanges its krsk_
key for a short-lived, tenant-scoped token.
Two decisions worth stating, because both look like omissions:
No audit row and no advisory lock. Migration 0003's audit trigger
serialises appends per workspace, and this endpoint is called once per
end-user session per five minutes — it scales with the vendor's customers,
not with the vendor. Auditing it would put the busiest control-plane endpoint
behind the same per-workspace lock that made an unrelated read take 22.7 s in
the week 2 review. The revocation is audited (POST /v1/tenants/:id/embed_tokens/revoke), which is the event an auditor asks
about; the mints are the vendor's own traffic.
SecretKeyGuard, not HotSecretKeyGuard. The hot cache's own contract
says it is for the rate-limit and idempotency endpoints and must not be
widened, and it should not be: a token minted with a krsk_ key revoked five
seconds ago is a five-minute credential, not one request.
POST /v1/embed_tokens
NoOriginGuard runs first and is the whole of report section 7.3's fifth
row. A browser cannot suppress Origin on a cross-origin request, so its
presence means a krsk_ key reached browser-delivered code — and the
answer has to say that loudly enough that someone rotates the key.
Answers 201 on success.
Authentication.
- Refused when the request carries an
Originheader: this is called from a backend, never from a browser. - A
krsk_secret key only. A person's session is refused on this plane.
Body. Validated by this schema, from the control plane's own source:
const MintEmbedToken = z.object({
project_id: z.uuid(),
env: Env,
tenant_id: z.uuid(),
scopes: z
.array(z.string().min(1).max(220))
.max(32)
.default(['keys:read'])
.refine((scopes) => scopes.every(isEmbedScope), {
message:
'must be keys:read, keys:write, or key_scope:<scope the session may put on a key>',
}),
/**
* Which of the workspace's registered origins this token is for. Omitted
* means all of them, which is the useful default for a vendor with one
* dashboard; naming a subset is for a vendor whose staging and production
* dashboards should not be able to replay each other's tokens.
*/
origins: z.array(z.string().min(1).max(255)).max(20).optional(),
});Shared validators
Defined once in packages/api/src/validation.ts and used by the schemas above.
const Env = z.enum(['live', 'test']);