# auth.md

Machine-readable registration and authorization guide for AI agents that need to
call Calvis on behalf of a user.

Calvis is a multi-agency security guard marketplace that connects customers with
vetted, licensed security agencies across 40+ US metros. This document tells your
agent how to obtain credentials and use them.

- **Audience:** AI agents acting on behalf of an authenticated Calvis customer,
  agency, or operator. There is no unauthenticated or machine-only access tier.
- **Protocol:** OAuth 2.1 — RFC 7591 dynamic client registration, then
  `authorization_code` with PKCE (S256). Public clients only; there is no client
  secret.
- **What you get:** a bearer token scoped to the granting user's own permissions,
  and access to the hosted MCP server at `https://mcp.calvis.com/mcp`.

---

## 1. Discover the endpoints

Fetch the protected resource metadata (RFC 9728), then the authorization server
metadata (RFC 8414) it points at. Read the endpoints from these documents at
runtime rather than hard-coding the URLs below.

```http
GET https://api.calvis.com/.well-known/oauth-protected-resource
```

```http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "resource": "https://api.calvis.com",
  "authorization_servers": ["https://api.calvis.com"],
  "scopes_supported": ["mcp"],
  "bearer_methods_supported": ["header"]
}
```

The same document is mirrored on the marketing origin at
`https://calvis.com/.well-known/oauth-protected-resource`.

For the MCP resource specifically:

```http
GET https://mcp.calvis.com/.well-known/oauth-protected-resource
```

```http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "resource": "https://mcp.calvis.com",
  "authorization_servers": ["https://api.calvis.com"],
  "scopes_supported": ["mcp"],
  "bearer_methods_supported": ["header"]
}
```

Both point at the same authorization server:

```http
GET https://api.calvis.com/.well-known/oauth-authorization-server
```

```http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "issuer": "https://api.calvis.com",
  "authorization_endpoint": "https://app.calvis.com/oauth/authorize",
  "token_endpoint": "https://api.calvis.com/api/v2/oauth/token",
  "registration_endpoint": "https://api.calvis.com/api/v2/oauth/register",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"],
  "scopes_supported": ["mcp"]
}
```

---

## 2. Register your client

Register once, then persist the `client_id`. Registration is open — no approval
step, no pre-issued key.

```http
POST https://api.calvis.com/api/v2/oauth/register
Content-Type: application/json

{
  "client_name": "Example Agent",
  "redirect_uris": ["http://127.0.0.1:8976/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "client_uri": "https://example.com",
  "logo_uri": "https://example.com/logo.png",
  "software_id": "example-agent",
  "software_version": "1.4.0"
}
```

```http
HTTP/1.1 201 Created
Content-Type: application/json

{
  "client_id": "cal_client_9f2c...",
  "client_name": "Example Agent",
  "redirect_uris": ["http://127.0.0.1:8976/callback"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"]
}
```

Required fields:

| Field | Type | Rule |
|---|---|---|
| `client_name` | string | Required, non-empty |
| `redirect_uris` | array of strings | Required, non-empty |

Optional: `client_uri`, `logo_uri`, `software_id`, `software_version`.

**Request `refresh_token` explicitly.** The server intersects your requested
`grant_types` with what it supports (`authorization_code`, `refresh_token`). Omit
`refresh_token` and you are registered for `authorization_code` only, which means
re-running the browser flow on every reconnect.

No client secret is issued. `token_endpoint_auth_method` is always `none`.

---

## 3. Send the user to the authorization page

Generate a PKCE verifier and its S256 challenge, then open this URL in the user's
browser. **S256 is the only supported challenge method** — a plain challenge is
rejected.

```
https://app.calvis.com/oauth/authorize
  ?response_type=code
  &client_id=cal_client_9f2c...
  &redirect_uri=http%3A%2F%2F127.0.0.1%3A8976%2Fcallback
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256
  &scope=mcp
  &state=<opaque-csrf-value>
```

The user signs in and explicitly approves the request. On approval the browser is
redirected to your `redirect_uri` with `code` and `state`. Verify `state` matches
what you sent before continuing.

`mcp` is the only scope. Do not request others.

---

## 4. Exchange the code for a token

```http
POST https://api.calvis.com/api/v2/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=<code from the redirect>
&redirect_uri=http%3A%2F%2F127.0.0.1%3A8976%2Fcallback
&client_id=cal_client_9f2c...
&code_verifier=<the PKCE verifier you generated in step 3>
```

```http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "access_token": "cpat_...",
  "token_type": "Bearer",
  "expires_in": 2592000,
  "refresh_token": "...",
  "scope": "mcp"
}
```

Send no client secret and no `Authorization` header on this request — the client
is public.

---

## 5. Call the API

Put the access token in the `Authorization` header. `bearer_methods_supported` is
`["header"]`; query-string and body-parameter delivery are not supported.

```http
POST https://mcp.calvis.com/mcp
Authorization: Bearer cpat_...
Content-Type: application/json
Accept: application/json, text/event-stream

{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}
```

The MCP server speaks **Streamable HTTP**. Point any MCP-capable client at
`https://mcp.calvis.com/mcp`.

An unauthenticated or expired request returns a 401 carrying the discovery
pointer, so a client that has lost its token can re-enter this flow at step 1:

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="calvis", resource_metadata="https://mcp.calvis.com/.well-known/oauth-protected-resource"
```

The access token is a Calvis Personal Access Token. It carries exactly the
permissions of the user who granted it — no more. An agent holding a customer's
token sees that customer's bookings; it does not see another customer's.

---

## 6. Refresh and revoke

| | Lifetime |
|---|---|
| Access token | 30 days |
| Refresh token | 90 days |

Refresh before expiry:

```http
POST https://api.calvis.com/api/v2/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=<refresh token>
&client_id=cal_client_9f2c...
```

The user revokes access from dashboard settings at `https://app.calvis.com`. The
session dies immediately — treat any 401 as final and restart the flow rather
than retrying with the same token.

---

## Errors

| Status | `error` | Cause |
|---|---|---|
| 400 | `invalid_client_metadata` | `client_name` missing or empty at registration |
| 400 | `invalid_redirect_uri` | `redirect_uris` missing, empty, or not an array |
| 400 | standard OAuth 2.1 codes | Token exchange failures — a used or expired code, a `code_verifier` that does not match the challenge, a missing or non-`S256` `code_challenge`. Branch on the `error` field of the response, not on the status alone |
| 401 | — | Missing, malformed, expired, or revoked bearer token. See `WWW-Authenticate` |

---

## Not implemented

Do not attempt these against Calvis — they do not exist and will not resolve:

- The WorkOS agentic-registration profile: ID-JAG, identity assertions,
  `anonymous` client registration, and the claim ceremony (`user_code` /
  `verification_uri`). **`authorization_code` + PKCE with a human approving in the
  browser is the only supported path.** Every token is granted by a person.
- A machine-to-machine grant. `client_credentials` and JWT-bearer are not in
  `grant_types_supported`.
- A `revocation_endpoint` or `jwks_uri`. Revocation is a user action in the
  dashboard; there is no programmatic revocation endpoint and no published JWKS.
- Scopes other than `mcp`.

---

## Related documents

| Document | Purpose |
|---|---|
| `https://calvis.com/llms.txt` | What Calvis is, services, coverage, pricing |
| `https://calvis.com/llms-full.txt` | Full service catalog and marketplace detail |
| `https://calvis.com/.well-known/api-catalog` | Machine-readable API index |
| `https://calvis.com/.well-known/mcp/server-card.json` | MCP server card |
| `https://api.calvis.com/api/v2/health/` | Service health |

Questions: https://calvis.com/#contact
