# Guarded browser HTTP reference

## Scope and authentication

This is the implemented same-origin browser contract, not an agent REST credential option. The controlled application origin is `https://app.agentcodex.io`. Current real Passport sign-in and authenticated notebook operation remain unverified end to end. Remote agent clients should use a future authorized MCP integration, not extract or replay browser cookies.

Machine schemas: [OpenAPI 3.1](/openapi.json). Its server origin is the dashboard, and operation paths already include their full route. There is no `/v1` prefix. Do not prepend one. Documentation and machine files are static assets on the public website; API calls belong to the dashboard origin.

Browser authentication uses the HttpOnly `__session` cookie under Firebase Hosting. All entry operations, including GET, require `X-Field-Notes-Csrf` matching the current session. Mutations require exact Origin `https://app.agentcodex.io`. An Origin header, when present on a read, must also match. No CORS or API-key/Bearer login is supported by these routes.

## Health

`GET /health` (also HEAD) returns `{"service":"field-notes","status":"browser-auth-configured"}` for a browser-configured runtime. The service name is a stable compatibility identifier. It reports construction, not successful authentication, database readiness or remote MCP availability. Other methods receive 405 with `Allow: GET, HEAD`.

## Browser sign-in routes

Use the dashboard's flow. These are protocol descriptions, not permission to automate owner confirmation.

- `POST /auth/passport/begin`, body `{}`: begins a browser-bound challenge and sets its HttpOnly cookie. Returns `clientId`, `nonce`, `signInFingerprint` and `expiresAt` in epoch milliseconds.
- `POST /auth/passport/callback`, body `{token, nonce}`: approved server-to-server Passport delivery, not a browser login form. Returns `message` and `confirmationCode`, both containing the one-time code, plus its actual `expiresAt` in epoch milliseconds. The message remains the code for Passport's existing `siteMessage` contract. This route is handled before browser Origin checks. Never paste the assertion into chat or reuse it as a notebook bearer.
- `POST /auth/passport/status`, body `{nonce}` with the original challenge cookie: returns `{status:"pending", expiresAt}` or `{status:"arrived", issuer, passportId, expiresAt}` and records that the identity was shown. This is the stored challenge deadline, which can shorten after arrival and never renews on a status check. It does not issue a session.
- `POST /auth/passport/confirm`, body `{nonce, confirmationCode, expectedPassportId}` with the original challenge cookie: checks the displayed exact identity and one-time code. Returns `{status:"authenticated", issuer, passportId, expiresAt, csrfToken}` and sets the session cookie. The response never exposes the session token.
- `POST /auth/passport/cancel`, body `{nonce}` with the original challenge cookie: cancels that challenge and clears the cookie; returns `{cancelled:true}`.
- `GET /auth/session`: no cookie returns `{status:"anonymous"}`; a valid cookie returns `{status:"authenticated", issuer, passportId, expiresAt, csrfToken}`. A syntactically valid cookie whose token is unknown, revoked or expired returns 401. A malformed, duplicate or oversized cookie is treated as absent by this route.
- `POST /auth/logout`, body `{}` with session cookie and CSRF: revokes the current browser session, clears the cookie, returns `{signedOut:true, scope:"current-session"}`.
- `POST /auth/logout-all`, body `{}` with session cookie and CSRF: revokes all current browser sessions for the exact identity, clears the cookie, returns `{signedOut:true, scope:"all-current-sessions"}`.

All mutation bodies above are strict JSON objects with no extra fields. Challenge begin/callback are rate-limited. Begin/status/confirm/cancel are challenge operations, not CSRF-authenticated notebook calls. Callback JSON has a 20 KiB body cap; the other browser JSON routes have a 512 KiB parser cap. Per-field limits still apply.

## Entry routes

These require the matching authenticated browser cookie and CSRF header. The server derives ownership. Never send `owner`, `issuer`, `passportId` or `agentId` as an ownership override.

- `POST /entries`: body `{requestId, entry}`. Returns HTTP 201 and `{entry}`. Same save contract and retry semantics as `field_notes_save`.
- `GET /entries/{id}`: returns HTTP 200 and `{entry}`. Same retrieval contract as `field_notes_get`.
- `PUT /entries/{id}`: body `{expectedRevision, entry}`. Returns HTTP 200 and `{entry}`. Full replacement with revision checks, not PATCH.
- `POST /entries/search`: body `{query?, tag?, relatedAgentId?, limit?, cursor?}`. Returns HTTP 200 and `{entries, nextCursor?, coverage}`. Empty object browses. This is not `GET /entries` or a URL query search.

See [data and limits](/docs/data.md), [MCP tool reference](/docs/mcp.md) and [errors](/docs/errors.md) for exact shared behavior. Error responses do not disclose whether a missing ID belongs to another codex. Private route responses carry `Cache-Control: no-store`; do not cache or log private bodies or credentials.

## Closed remote boundaries

The default function responds 501 at `/mcp` because remote OAuth is not configured. The current website/dashboard Hosting rewrites do not publish that path or OAuth metadata. Do not infer a canonical MCP resource, auth-server metadata URL, authorize/token route or client registration endpoint from candidate source. These docs intentionally do not present those as working public operations.
