curl --request POST \
--url https://app.testorim.com/api/keys \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "GitHub Actions"
}
'{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"prefix": "<string>",
"key": "tst_live_a1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6",
"createdAt": "2023-11-07T05:31:56Z"
}Mint a new API key (browser session only)
An API key cannot create another API key. The handler inspects the
Authorizationheader on this very request and returns403when it starts withtst_live_, deliberately limiting the blast radius of a leaked key. Mint keys from the dashboard (which authenticates with a Clerk session JWT). The operation is documented here for completeness, not because CI can call it.
The new key is bound to the caller’s user and to their currently active organization, which then becomes the only org that key can act on.
The plaintext key is returned once, in this response, and is never recoverable. Only its SHA-256 hash is stored. Copy it immediately.
expiresAt accepts an ISO-8601 string or epoch milliseconds; it must
parse and must be in the future.
curl --request POST \
--url https://app.testorim.com/api/keys \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "GitHub Actions"
}
'{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "<string>",
"prefix": "<string>",
"key": "tst_live_a1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6",
"createdAt": "2023-11-07T05:31:56Z"
}Authorizations
Send Authorization: Bearer tst_live_….
Format (services/api-keys.ts): the literal prefix tst_live_
followed by 24 random bytes rendered as 32 base64url characters.
Only the SHA-256 hash is stored server-side. The shape check that
routes a token down the API-key path rather than the Clerk JWT path
requires the tst_live_ prefix and a total length of at least 25
characters.
Keys are minted in the dashboard at /settings/team. The same header
also accepts a Clerk session JWT, which is how the web app
authenticates, but the JWT path is out of scope for this document.
Body
Label for the key, e.g. GitHub Actions. Trimmed, then truncated to 80 characters at storage.
1 - 80Optional expiry as an ISO-8601 string (max 80 characters) or a positive integer of epoch milliseconds. Must parse and must be in the future. Null or omitted means the key never expires.
Response
Key created. This is the only response that ever contains the plaintext key.
The 201 body of POST /api/keys. The only place the plaintext key ever appears.

