# auth.md

Norbelys is a cold-email platform. Agents that call the REST API (`api.norbelys.com`) or the
remote MCP server (`mcp.norbelys.com`) authenticate with Clerk-issued OAuth tokens or
organization-scoped API keys. This document follows the WorkOS `auth.md` convention
(https://workos.com/auth-md): Discover → Pick a method → Register → Claim → Use → Errors →
Revocation.

## Discover

Machine-readable discovery lives at predictable URLs:

- Protected resource (RFC 9728): `https://norbelys.com/.well-known/oauth-protected-resource`
- Authorization server (RFC 8414): `https://norbelys.com/.well-known/oauth-authorization-server`
  — includes the `agent_auth` block described below.
- OpenID configuration: `https://norbelys.com/.well-known/openid-configuration`
- API catalog (RFC 9727): `https://norbelys.com/.well-known/api-catalog`
- MCP server card: `https://norbelys.com/.well-known/mcp/server-card.json`
- OpenAPI: `https://api.norbelys.com/openapi.json`

| Resource | Base URL | Protected-resource metadata |
| --- | --- | --- |
| Site | `https://norbelys.com/` | `https://norbelys.com/.well-known/oauth-protected-resource` |
| REST API | `https://api.norbelys.com/` | `https://api.norbelys.com/.well-known/oauth-protected-resource` |
| MCP server | `https://mcp.norbelys.com/mcp` | `https://mcp.norbelys.com/.well-known/oauth-protected-resource` |

A protected endpoint answers an unauthenticated request with `401` and a
`WWW-Authenticate: Bearer resource_metadata="…"` header pointing at its protected-resource
metadata — start there.

## Pick a method

- **OAuth 2.1** (interactive agents, IDE assistants): browser consent, with the workspace
  selected at consent time. Best when a human is in the loop.
- **Organization-scoped API key** (`ak_…`) (headless, CI): no browser, pins one workspace.

The authorization-server metadata carries an `agent_auth` block advertising both under
`identity_types_supported`: `anonymous` (API key) and `identity_assertion` (OAuth, including the
`urn:ietf:params:oauth:token-type:id-jag` (id-jag) assertion type).

## Register

Interactive agents self-register via Clerk Dynamic Client Registration:

1. Fetch the protected-resource metadata and follow `authorization_servers` to the
   authorization-server metadata.
2. Register a client at `agent_auth.register_uri`
   (`https://clerk.norbelys.com/oauth/register`).
3. Run the OAuth 2.1 authorization-code flow with PKCE (S256) and select an organization at
   the consent screen.

## Claim

For headless / CI use, a human claims a credential without a browser:

1. Create an **organization-scoped API key** (`ak_…`) in the Norbelys app —
   `agent_auth.claim_uri` → `https://app.norbelys.com/sign-up`.
2. The key pins the workspace (the tenant); no OAuth flow is required.

Scopes: `profile`, `email`, `user:org:read`.

## Use the credential

- Header: `Authorization: Bearer <token>` — an OAuth access token or an `ak_…` key.
- REST base: `https://api.norbelys.com/v1/…`
- MCP endpoint: `https://mcp.norbelys.com/mcp`
- Send a non-default `User-Agent` on scripted REST calls (Cloudflare blocks `Python-urllib/*`).

## Errors

Failures return `{ "error": { "type", "code", "message", "hint?", "doc_url?" } }` with the
matching HTTP status. Auth-relevant cases:

- `401` `authentication_error` — missing or invalid credential. Re-read the `WWW-Authenticate`
  header and re-authenticate.
- `403` `permission_error` — authenticated, but not permitted for this workspace or scope.
- `429` `rate_limit` — retry after the interval in the `Retry-After` response header.

## Revocation

- **API keys**: revoke in the Norbelys app (Settings → API keys). The key stops working
  immediately.
- **OAuth tokens**: revoke access from the connected-apps screen; Clerk invalidates the
  outstanding refresh and access tokens.

## Support

- API docs: https://docs.norbelys.com/
- Contact: hello@norbelys.com
