# auth.md — authenticating agents with Plori

Plori hosts AI agents, each on its own cloud computer, and exposes a curated slice of its
API to external agents over the **Model Context Protocol (MCP)** and a REST API. This
document tells an automated agent how to obtain and use credentials.

## Protected resource

- **MCP server:** `https://api.plori.ai/mcp` (Streamable HTTP)
- **REST API:** `https://api.plori.ai/v1`
- **Protected Resource Metadata (RFC 9728):** `https://api.plori.ai/.well-known/oauth-protected-resource/mcp`
  (the §3.1 canonical path for the resource above; also served at `https://api.plori.ai/.well-known/oauth-protected-resource`)

## Identity & audience

A credential is scoped to exactly one Plori account (the tenant). Every MCP tool call and
REST request acts as that account; there is no cross-account access. OAuth access tokens are
audience-bound (RFC 8707) to the MCP server above and are rejected anywhere else.

## Automated authentication (OAuth 2.1) — recommended

Plori runs an embedded OAuth 2.1 authorization server, so a compliant MCP client (Claude,
ChatGPT, Cursor, VS Code, …) can connect with no hand-copied key. The flow is the MCP
authorization handshake:

1. **Discover the resource.** A request to `https://api.plori.ai/mcp` without a token returns
   `401` with `WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource/mcp"`
   and a JSON body repeating that URL. That document lists the authorization server under
   `authorization_servers`.
2. **Discover the authorization server (RFC 8414 / OIDC).** Fetch the AS metadata:
   - `https://api.plori.ai/.well-known/oauth-authorization-server/oauth`, or
   - `https://api.plori.ai/oauth/.well-known/openid-configuration`
   It advertises `authorization_endpoint`, `token_endpoint`, `registration_endpoint`,
   `jwks_uri`, and `code_challenge_methods_supported: ["S256"]`.
3. **Register (RFC 7591 Dynamic Client Registration).** `POST` your client metadata
   (`redirect_uris`, `client_name`) to `https://api.plori.ai/oauth/register` to receive a `client_id`.
   Registration is open; public clients use `token_endpoint_auth_method: "none"` + PKCE.
4. **Authorize (Authorization Code + PKCE).** Send the user to `authorization_endpoint`
   with `code_challenge` (`S256`), `resource=https://api.plori.ai/mcp` (RFC 8707), and your
   `redirect_uri`. The user signs in once with an email one-time code; signing in is the
   consent step.
5. **Exchange for tokens.** `POST` the `code` + `code_verifier` to `token_endpoint` to
   get a JWT access token (audience `https://api.plori.ai/mcp`) and a rotating refresh token. Present
   the access token as `Authorization: Bearer <token>` to `https://api.plori.ai/mcp`.

```yaml
agent_auth:
  type: oauth2
  flows: [authorization_code]
  pkce: required            # S256
  issuer: https://api.plori.ai/oauth
  metadata_uri: https://api.plori.ai/.well-known/oauth-authorization-server/oauth
  register_uri: https://api.plori.ai/oauth/register
  resource: https://api.plori.ai/mcp
  scopes_supported: [openid, offline_access, mcp]
```

Most MCP clients automate steps 1–5; point the client at `https://api.plori.ai/mcp` and approve the
one-time sign-in.

## Manual authentication (API key) — alternative

For scripts, servers, or clients without OAuth support, the account owner can provision a
long-lived key:

1. Sign up or sign in at https://plori.ai (email one-time code; the API endpoints are
   `POST https://api.plori.ai/v1/auth/otp/start` then `POST https://api.plori.ai/v1/auth/otp/verify`).
2. Create an API key in the dashboard: **Settings → API keys**. The raw key
   (`plori_sk_…`) is shown once — copy it then.

- **Presentation:** HTTP header `Authorization: Bearer plori_sk_…` on every request — the
  only supported method (`bearer_methods_supported: ["header"]`).
- **Validation:** the key is re-checked on every request, so revoking it takes effect at once.
- **Scope:** full access to the owning account's agents, runs, usage and billing.

## Example (MCP client config, API key)

```json
{
  "mcpServers": {
    "plori": {
      "type": "streamable-http",
      "url": "https://api.plori.ai/mcp",
      "headers": { "Authorization": "Bearer plori_sk_YOUR_KEY" }
    }
  }
}
```

## Revocation

- **OAuth:** refresh tokens rotate on every use; revoke a token at the `revocation_endpoint`
  in the AS metadata. Access tokens are short-lived (one hour).
- **API key:** delete it in **Settings → API keys**; it stops working on the next request.
