API reference
Endpoint-by-endpoint reference for every public Tesska endpoint. Requests and responses are JSON; the broker only ever delivers short-lived tokens or end-to-end ciphertext — raw credentials never leave the vault.
Authentication
Every endpoint except the public catalog uses Bearer auth. Two kinds of key work:
- Project-scoped key (prefix tsk_): issued per project in the console's Token Center; it can only reach credentials and scopes explicitly granted to that project.
- Connect Tesska access_token: a per-user key obtained by third-party apps through the OAuth authorization-code flow; use it directly as a Bearer token against /v1/*.
- GET /v1/platforms is a public catalog and needs no auth.
curl https://tesska.com/v1/grants \
-H "Authorization: Bearer tsk_live_3f9a..."Keys never buy raw secrets.By default a Bearer key only exchanges for short-lived access tokens or end-to-end ciphertext; raw delivery requires an explicit per-asset opt-in plus a global switch that is off by default.
POST /v1/login
The core broker endpoint: exchange one granted platform account for one access. The response shape depends on how the credential is held — Mode A returns a short-lived token (or raw, if explicitly enabled); Mode B returns pending_approval and pushes the owner's phone. Body fields:
- platform (required) — platform id, see GET /v1/platforms.
- account (required) — account identifier of the vaulted credential.
- scope (optional) — string or string[]; defaults to the grant's full scopes, anything beyond them returns 403.
- clientPubKey (required for Mode B) — the caller's ephemeral ECDH-P256 public key; on approval the credential is sealed to it in the owner's browser (HKDF-SHA256 + AES-256-GCM).
curl -X POST https://tesska.com/v1/login \
-H "Authorization: Bearer tsk_..." \
-H "Content-Type: application/json" \
-d '{"platform":"github","account":"deploy-bot","scope":["contents:read"]}'
# Mode A - short-lived token (never the raw secret)
{ "kind": "token", "token": "...", "expiresAt": "2026-07-05T12:30:00.000Z",
"scopes": ["contents:read"] }
# Mode A - raw delivery (per-asset opt-in + global switch, off by default)
{ "kind": "raw", "credential": { ... }, "scopes": ["contents:read"] }
# Mode B - approval pushed to the owner's phone
{ "kind": "pending_approval", "requestId": "...", "expiresAt": "..." }GET /v1/grants
Introspection: lists the grants this key can call (project × credential × scopes). The response contains no secrets or tokens.
curl https://tesska.com/v1/grants \
-H "Authorization: Bearer tsk_..."
{ "project": "ci-pipeline",
"grants": [ { "platform": "github", "account": "deploy-bot",
"scopes": ["contents:read", "issues:write"] } ] }GET /v1/platforms
Public platform catalog: metadata for supported and reserved adapters, no auth required. status is ready (available) or planned (reserved); missing platforms can be requested from the console.
curl https://tesska.com/v1/platforms
{ "platforms": [ { "id": "github", "label": "GitHub",
"authKind": "github-app", "status": "ready",
"scopes": ["contents:read", "contents:write", "issues:read", "..."],
"credentialHint": "..." } ] }GET /v1/approvals/:id
Poll a Mode B approval. status is pending / approved / denied / expired; an id that doesn't belong to this key's project returns 404.
- On approved you get resultCt + resultMeta: the credential was decrypted in the owner's browser and sealed to your clientPubKey — only your ephemeral private key can open it.
- The ciphertext is delivered exactly once: the server burns it after the first read, and later polls return only the status without resultCt.
- The server holds zero plaintext throughout — everything it stores and relays is ciphertext.
curl https://tesska.com/v1/approvals/<requestId> \
-H "Authorization: Bearer tsk_..."
{ "status": "pending" }
# After approval - E2E ciphertext, delivered exactly once
{ "status": "approved", "resultCt": "<ciphertext>", "resultMeta": "..." }GET /oauth/authorize
The Connect Tesska authorization-code endpoint (a browser redirect, not an API call), with PKCE S256 enforced. An unregistered client_id or a redirect_uri outside the allowlist is blocked outright without redirecting; other protocol errors bounce back to redirect_uri as error=unsupported_response_type / invalid_request / invalid_scope. Query parameters:
- client_id (required) — obtained by registering in the console's Developer apps.
- redirect_uri (required) — must exactly match the registered allowlist.
- response_type (required) — always code.
- scope (required) — space-delimited, must stay within the app's allowed scopes.
- state (recommended) — echoed back verbatim, protects against CSRF.
- code_challenge (required) — BASE64URL(SHA256(code_verifier)).
- code_challenge_method (required) — only S256 is accepted.
GET https://tesska.com/oauth/authorize
?client_id=<client_id>
&redirect_uri=https://app.example.com/callback
&response_type=code
&scope=login
&state=af0ifjsldkj
&code_challenge=<BASE64URL(SHA256(code_verifier))>
&code_challenge_method=S256POST /oauth/token
Exchange the authorization code for an access_token. application/x-www-form-urlencoded (JSON also accepted); client credentials go in the body or via HTTP Basic. The access_token is a per-user scoped key: use it directly as a Bearer token against /v1/*; it is long-lived but the user can revoke it anytime under Connected apps. Parameters:
- grant_type (required) — authorization_code.
- code (required) — the authorization code from the redirect.
- code_verifier (required) — the PKCE plaintext verifier.
- redirect_uri (required) — same as in the authorization request.
- client_id / client_secret — body or HTTP Basic; the client_secret is shown only once at registration.
curl -X POST https://tesska.com/oauth/token \
-u "<client_id>:<client_secret>" \
-d grant_type=authorization_code \
-d code=<code> \
-d code_verifier=<code_verifier> \
-d redirect_uri=https://app.example.com/callback
{ "access_token": "...", "token_type": "Bearer",
"expires_in": 31536000, "scope": "login" }Error codes
Errors from /v1/* are uniformly { "error": "..." } with the matching HTTP status; the OAuth endpoints follow OAuth 2.0 error conventions (error=invalid_request and friends).
- 400 — body is not valid JSON, platform/account missing, unsupported platform, or Mode B call without clientPubKey.
- 401 — Bearer key missing, invalid, or revoked.
- 403 — no such grant, scope exceeded, platform not on the project allowlist, credential frozen, or raw delivery not enabled.
- 404 — approval request not found, or not owned by this key's project.
- 429 — per-minute rate limit of the grant exceeded.
- 501 — capability not live yet (e.g. proxy-login session delivery, coming soon).
- 502 — upstream platform failed to mint the token.
Audit
Every call — success or failure — writes an audit event: caller project and key, target platform and account, scopes, outcome, and other metadata, traceable in the console. Anomalous patterns (bursts of failures, unusual calls) trigger anomaly detection and push alerts.
No secrets in the audit trail.Audit events carry metadata only; credential plaintext is never written to the audit log.
Webhooks & SDKsPlanned
Outbound webhooks and official SDKs are not released yet — this documentation only describes what's live. In the meantime:
- Approval results: poll GET /v1/approvals/:id (see above).
- SDKs: every endpoint is plain HTTPS + JSON, so any HTTP client works with the examples on this page.