# auth.md

You are an agent. This document tells you how to authenticate to **Jutusta**
(https://jutusta.ee) so you can call its speech API — text-to-speech, transcription, and
translation. Follow the path that matches what you have; do not skip ahead.

Jutusta is a standard **OAuth 2.1 / OIDC authorization server**. There are two
ways to get a bearer credential:

1. **Act on a user's behalf (OAuth 2.1).** Register as a client, then run the
   authorization-code + PKCE flow. The user signs in and consents; you receive
   an access token scoped to their account. This is the right path for an agent
   platform serving many users.
2. **Act as a single developer (API key).** A human signs in to Jutusta and
   mints an opaque API key (`sk_jt_…`) from the dashboard. Simplest path for
   your own bot. No OAuth dance.

Both credentials are presented the same way at call time (Step 4).

## Step 1 — Discover

Two machine-readable documents describe everything below. Fetch them first:

- **Protected Resource Metadata** (RFC 9728):
  `https://jutusta.ee/.well-known/oauth-protected-resource`
  Tells you the API `resource`, the `authorization_servers`, the
  `scopes_supported`, and that bearer tokens go in the `Authorization` header.
  A 401 from `https://jutusta.ee/api/v1/*` also points here via its
  `WWW-Authenticate: Bearer … resource_metadata="…"` header.
- **Authorization Server Metadata** (RFC 8414):
  `https://jutusta.ee/.well-known/oauth-authorization-server`
  Carries the standard `authorization_endpoint` / `token_endpoint` /
  `registration_endpoint` plus an `agent_auth` block summarizing this file.

Prefer the values you read from those documents over the literals below.

## Step 2 — Pick a method

- You want to act on **a Jutusta user's behalf** → OAuth 2.1 (Step 3a). The user
  is in the loop: they sign in and approve the scopes on a consent screen.
- You are wiring up **your own** integration and can have a human sign in once →
  API key (Step 3b).

Jutusta does **not** support anonymous registration, ID-JAG identity
assertions, or an email "claim ceremony". If your platform expects one of those,
fall back to the OAuth flow (a human consents) or an API key.

## Step 3a — OAuth 2.1 (act on a user's behalf)

1. **Register a client** (RFC 7591 Dynamic Client Registration). POST your
   client metadata (`redirect_uris`, `client_name`, etc.) to:
   ```http
   POST https://jutusta.ee/api/auth/mcp/register
   Content-Type: application/json

   { "redirect_uris": ["https://your-agent.example/callback"], "client_name": "Your Agent" }
   ```
   You get back a `client_id` (and a `client_secret` unless you registered as
   a public `none`-auth client using PKCE).
2. **Authorize.** Send the user to the authorization endpoint with PKCE:
   ```
   https://jutusta.ee/api/auth/mcp/authorize?response_type=code&client_id=<id>&redirect_uri=<uri>
     &scope=openid%20profile%20email%20offline_access%20tts&code_challenge=<S256>&code_challenge_method=S256&state=<state>
   ```
   The user signs in to Jutusta and approves the requested scopes on the consent
   screen. On approval you receive `code` at your `redirect_uri`.
3. **Exchange the code** for an access token:
   ```http
   POST https://jutusta.ee/api/auth/mcp/token
   Content-Type: application/x-www-form-urlencoded

   grant_type=authorization_code&code=<code>&redirect_uri=<uri>&client_id=<id>&code_verifier=<verifier>
   ```
   The response is a standard OAuth token response (`access_token`,
   `token_type: "Bearer"`, `expires_in`, `scope`, and a `refresh_token` when
   `offline_access` was granted). Use `refresh_token` to mint fresh access
   tokens without sending the user back through consent.

## Step 3b — API key (act as a single developer)

A signed-in user creates and revokes API keys at `https://jutusta.ee/app/developers`.
Keys are prefixed `sk_jt_`. There is no programmatic minting — this path
assumes a human holds the account.

## Step 4 — Use the credential

Both an OAuth access token and an API key are presented as a bearer credential:

```http
POST https://jutusta.ee/api/v1/text-to-speech/maarja
Authorization: Bearer <oauth-access-token | sk_jt_…>
Content-Type: application/json

{ "text": "Tere!" }
```

The REST API (`https://jutusta.ee/api/v1/*`) is ElevenLabs-compatible and also accepts
the key via the `xi-api-key: sk_jt_…` header. There is a native **MCP**
endpoint at `https://jutusta.ee/mcp` (Streamable-HTTP, JSON-RPC) exposing a
`text_to_speech` tool, guarded by the same bearer token.

Calls that synthesize or transcribe **spend credits from the authorizing user's
account** and require the `tts` scope. Discovery-only scopes
(`openid`, `profile`, `email`) let you identify the user but not spend.

Full machine-readable API description: `https://jutusta.ee/api/v1/openapi.json`.
Human developer guide: `https://jutusta.ee/en/developers`.

## Scopes

`openid profile email offline_access tts`

- `tts` — use speech synthesis/transcription on the user's behalf
  (**spends their credits**). Request this for any real work.
- `openid`, `profile`, `email` — identify the user.
- `offline_access` — receive a `refresh_token`.

## Revocation

- **OAuth tokens** — the user revokes a connected agent's access from their
  Jutusta account; access tokens are opaque and validated server-side, so
  revocation takes effect immediately. When a token stops working, re-run the
  Step 3a token exchange (or refresh); if that fails, the grant was revoked —
  send the user back through Step 3a.
- **API keys** — the owning user deletes the key at `https://jutusta.ee/app/developers`.

## Errors

- `401` on `https://jutusta.ee/api/v1/*` or `https://jutusta.ee/mcp` → missing/expired/revoked
  credential. Follow `WWW-Authenticate` back to the Protected Resource Metadata
  and re-authorize.
- `402` → the authorizing account is out of credits. A human must top up at
  `https://jutusta.ee/app`.
- `403` with an `insufficient_scope` hint → your token lacks `tts`;
  re-authorize requesting it.
