Short-lived tokens
POST /v1/login is the broker's core endpoint: exchange your project-scoped key for a short-lived access token. Callers only ever receive temporary tokens — never the original secret stored in the vault.
Exchange a token
Issue a project key (prefixed tsk_) in the console under Token Center, then call /v1/login with it as a Bearer token, naming the platform and account. For mode-A (direct-issue) credentials, the broker opens the envelope server-side, obtains a short-lived access token via the platform adapter, and returns it synchronously:
curl -X POST https://tesska.com/v1/login \
-H "Authorization: Bearer tsk_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"platform": "github",
"account": "deploy-bot",
"scope": ["repo:read"]
}'Response
On success you get kind: "token":
- token — a short-lived platform access token (e.g. a GitHub App installation token) that expires on its own
- If the target credential is mode B (approval required), the response is {"kind":"pending_approval","requestId","expiresAt"} instead: the owner's phone gets a push, then you poll GET /v1/approvals/:id for the result
{
"kind": "token",
"token": "ghs_xxxxxxxxxxxxxxxx",
"expiresAt": "2026-07-05T12:34:56.000Z",
"scopes": ["repo:read"]
}The response never contains the original credential.Mode-A secrets are held under envelope encryption (AES-256-GCM) and decrypted server-side only for the instant of the exchange. The one exception is raw delivery, which is explicitly opt-in and off by default (see below).
What expiresAt means
expiresAt is the token's expiry instant, as an ISO 8601 timestamp in UTC.
- After expiry the token simply stops working. Call POST /v1/login again for a fresh one — there is no refresh endpoint, and none is needed
- The lifetime never exceeds the token-TTL cap configured on the grant
- Leave headroom for clock skew: treat the token as expired ~60 seconds early
Narrowing with scope
scope accepts a string or an array of strings and narrows this token to a subset of what the grant allows; omit it to get the grant's full scopes. Request only the minimum this call actually needs — anything beyond the grant is rejected with 403.
// grant covers ["repo:read", "repo:write"] — request only what you need
{
"platform": "github",
"account": "deploy-bot",
"scope": "repo:read"
}Error codes
Errors come back as { "error": "..." } with the matching status code:
- 400 — body is not valid JSON, or platform / account is missing
- 401 — Bearer key missing, invalid, or revoked
- 403 — this key has no grant for the credential; or the credential is not enabled for calls; or it is frozen; or the platform is not on the project's allowlist
- 429 — the grant's per-minute rate limit was exceeded; retry later
Raw delivery (off by default)
A few scenarios cannot be served with a short-lived token and can only hand over the secret itself. Raw delivery requires an explicit per-asset setting and a global switch — both off by default. When enabled, /v1/login returns:
- A raw credential leaves Tesska's protection the moment it is delivered: never write it to disk or logs, discard it right after use
- If a short-lived token can do the job, keep raw off
{
"kind": "raw",
"credential": "...",
"scopes": ["repo:read"]
}