# Omphalis auth.md

Connecting an agent to Omphalis

Omphalis exposes one agent-facing API: an MCP server over Streamable HTTP that
reads and writes a single user's Library. Every call is scoped to the user who
authorized it. There is no tenant-wide or admin token.

- **MCP endpoint:** `https://app.omphalis.ai/api/v2/mcp`
- **Transport:** Streamable HTTP
- **Authorization:** OAuth 2.1, authorization code with PKCE
- **Machine-readable metadata:** [`https://app.omphalis.ai/.well-known/oauth-protected-resource`](https://app.omphalis.ai/.well-known/oauth-protected-resource) (RFC 9728)
- **API catalog:** [`https://omphalis.ai/.well-known/api-catalog`](https://omphalis.ai/.well-known/api-catalog) (RFC 9727)

This page is prose for a human reviewer. Where it and the metadata documents
disagree, the metadata documents are correct: treat any disagreement as a bug
and report it to hello@omphalis.ai.

## Getting a token

1. **Discover.** Call the MCP endpoint without credentials. It answers `401`
   with a `WWW-Authenticate` challenge carrying `resource_metadata`, per RFC
   9728. Fetch that URL to learn the authorization server. Do not hard-code the
   endpoints below; read them from metadata so a future move does not break you.
2. **Register.** Dynamic client registration (RFC 7591) is open at the
   authorization server's `registration_endpoint`. Register once and keep the
   returned `client_id`. Public clients (no secret) are supported and are the
   right choice for anything that cannot keep a secret.
3. **Authorize.** Run the authorization-code flow with PKCE (`S256`; `plain`
   is rejected). Request only the scopes you need. Pass `resource=https://app.omphalis.ai/api/v2/mcp`
   (RFC 8707) so the issued token is audience-bound to this resource.
4. **Call.** Send the access token as `Authorization: Bearer <token>`. It is
   the only accepted method; tokens in query strings or request bodies are
   rejected.

A user must have an Omphalis account and must approve the scopes on the consent
screen. There is no way to provision access on a user's behalf.

## Scopes

Request the narrowest set that does the job. Over-requesting is the most common
reason a user declines the consent screen.

| Scope | Grants |
| --- | --- |
| `library:read` | Read saved items and their content |
| `library:write` | Save new items, create Collections, and organize items |
| `library:synthesize` | Answer questions across the Library, with citations |
| `collections:read` | Read Collections and their membership |
| `strata:read` | Read the structural moments of an item |
| `marks:read` | Read annotations and notes |
| `marks:write` | Create annotations and attach notes |
| `connections:read` | Read cross-item connections |
| `changes:read` | Read the feed of structural changes to the Library |
| `notebooks:read` | Read your notebooks |
| `notebooks:write` | Create notebooks and add to them |
| `search:read` | Search across the Library |

## Tools and the scope each needs

| Tool | Scope | Kind |
| --- | --- | --- |
| `library_list` | `library:read` | Read |
| `library_get` | `library:read` | Read |
| `library_save` | `library:write` | Write |
| `collections_list` | `collections:read` | Read |
| `collection_items` | `collections:read` | Read |
| `strata_get` | `strata:read` | Read |
| `marks_get` | `marks:read` | Read |
| `changes_list` | `changes:read` | Read |
| `marks_create` | `marks:write` | Write |
| `marks_add_note` | `marks:write` | Write |
| `connections_for_item` | `connections:read` | Read |
| `connections_list` | `connections:read` | Read |
| `search_library` | `search:read` | Read |
| `library_ask` | `library:synthesize` | Read |
| `library_organize` | `library:write` | Write |
| `collections_create` | `library:write` | Write |
| `spaces_list` | `collections:read` | Read |
| `tags_list` | `library:read` | Read |
| `notebooks_list` | `notebooks:read` | Read |
| `notebooks_get` | `notebooks:read` | Read |
| `notebooks_create` | `notebooks:write` | Write |
| `notebooks_append` | `notebooks:write` | Write |

## Plan requirements

Not every tool is available on every plan, and this is enforced at the server,
not at the consent screen. A free account can hold a token carrying
`library:write`, and the write will still be refused.

- **Read tools are available on every plan**, including free.
- **`library_save`, `marks_create`, and `library_ask` require a paid plan.**
  Note that `library_ask` is read-shaped but paid-gated, because it is the
  expensive one.

A call blocked by this gate fails with a message naming the plan requirement.
It is an account state, not a transient error: retrying will not clear it, and
the fix is for the user to upgrade. Surface that to them rather than looping.

## Expiry, refresh, and revocation

- **Access tokens are short-lived** — 15 minutes by default. Treat any `401`
  as "refresh once and retry", not as a permanent failure.
- **A refresh token is always issued** with the authorization-code exchange.
  There is no `offline_access` scope to request; do not send one.
- **Refresh tokens rotate.** Each refresh returns a new refresh token and
  invalidates the one you presented. Persist the new one before using it.
- **Replaying a rotated refresh token revokes the entire chain.** This is
  deliberate: a reused token is the standard theft signal, and the whole
  lineage is killed rather than the single token. If you lose a rotation to a
  crash mid-write, re-authorize; do not retry the old token.
- **Revocation** is available at the authorization server's
  `revocation_endpoint` (RFC 7009). Revoke on sign-out rather than letting
  tokens expire.
- **The user can revoke you at any time** from Settings in the Omphalis app.
  After that every call fails and refresh fails permanently. Do not retry in a
  loop; surface a reconnect prompt.
- **Reconnecting replaces rather than stacks.** A fresh authorization for the
  same app supersedes that user's prior grant for it, so the user sees one
  connection, not a pile of them.

## Rate limits

One flat budget: **120 requests per rolling 60-second window, per user.** It is
an anti-abuse guard, not a pricing lever, so it does not vary by tool, by
read-versus-write, or by plan. It is keyed on the user, not the client, so
several agents authorized by the same person share the one budget.

Exceeding it returns a rate-limit error carrying a `Retry-After` of between 1
and 60 seconds: the time until the window actually has room. Honour it. A
rejected request is not counted against the window, so a client that backs off
correctly recovers at exactly that moment, but one that hammers never does.

## Handling failure well

- **Unauthorized** — refresh once, then re-authorize. Never loop.
- **Forbidden / insufficient scope** — the token is valid but lacks the scope.
  Re-authorize asking for it; do not retry the same call.
- **Rate limited** — back off for `Retry-After` seconds.
- **Plan-gated** — do not retry. The user must upgrade.
- **Server error** — retry with exponential backoff and jitter, capped at 3
  attempts.

## What you may do with the content

Content in a user's Library is theirs, and much of it is third-party material
they saved for personal use. Use it to serve that user's request. Do not train
on it, redistribute it, or retain it beyond what the user's request needs.

Questions: hello@omphalis.ai
