# info.description
Programmatic access to the RRM Academy research library and editorial guardrails, exposed via the Model Context Protocol (MCP). thousands of peer-reviewed articles on restorative reproductive medicine, NaProTechnology, fertility awareness-based methods, endometriosis, PCOS, and related women's health topics. Clinical content curated under the direction of Dr. Naomi Whittaker, MD.

# servers[].description
Apex MCP server (high-level RAG, semantic search, editorial guardrails). Bearer auth, self-service keys at https://rrmacademy.org/account/mcp-keys.

# servers[].description
Library MCP server (low-level read-only structured access to the library: status, get_article, search_metadata). Public, no auth.

# servers[].description
RRM Academy Pages (ask endpoint, articles list, articles bulk, sandbox).

# x-idempotency-policy.key_format
16-128 printable ASCII characters; recommend a 36-character UUID v4

# webhooks.stripeEvent.post.description
Stripe posts billing events to https://rrmacademy.org/api/stripe-webhook . The handler verifies the `Stripe-Signature` header (HMAC-SHA256 over the raw request body, prefixed with a timestamp, per https://docs.stripe.com/webhooks/signatures) before processing. Requests with a missing or invalid signature are rejected with 400. The handler is idempotent: replayed events with the same `id` short-circuit on a dedup KV record. Accepted event types: `checkout.session.completed`, `checkout.session.expired`, `customer.subscription.updated`, `customer.subscription.deleted`, `invoice.payment_failed`, `charge.refunded`. This is the only inbound webhook surface; rrmacademy.org does not currently emit outbound webhooks to consumers.

# webhooks.stripeEvent.post.parameters[].description
Stripe HMAC-SHA256 signature over the raw POST body. Format: `t=<unix_ts>,v1=<hex_hmac>`. Verified by `stripe.webhooks.constructEventAsync` using the rotating webhook signing secret. See https://docs.stripe.com/webhooks/signatures.

# webhooks.stripeEvent.post.responses.200.description
Event accepted (verified, dispatched to the matching handler, or idempotently skipped).

# webhooks.stripeEvent.post.responses.400.description
Missing or malformed `Stripe-Signature` header, malformed JSON body, or signature failed HMAC verification.

# webhooks.stripeEvent.post.responses.500.description
Internal error while processing the event. Stripe will retry per its retry policy.

# paths./api/ask.get.description
Returns machine-readable capability JSON describing the /api/ask endpoint: supported methods, auth model, streaming transport, request/response shape, and guardrails. Unauthenticated. Cacheable for 1 hour.

# paths./api/ask.post.summary
Conversational AI over the RRM Research Library

# paths./api/ask.post.description
Answers questions scoped to restorative reproductive medicine using the RRM Library corpus. Two auth paths:

**Session-authenticated (20 req/day):** Include the `session` cookie from `/api/auth/login`. Supports both JSON and SSE response formats based on the `Accept` header.

**Anonymous (2 req/day/IP):** No cookie required. Always returns `text/event-stream`. Rate-limited per IP.

Set `Accept: text/event-stream` to receive a streaming SSE response regardless of auth state.

# paths./api/ask.post.responses.200.description
Answer with citations. Content-Type is `application/json` for authenticated requests without SSE Accept header, or `text/event-stream` for anonymous requests and any SSE-accepting request.

# paths./api/ask.post.responses.200.content.text/event-stream.schema.description
SSE stream. Two events: `data: <AskResponse JSON>` then `data: [DONE]`

# paths./api/ask.post.responses.400.description
Invalid input (message missing, wrong type, or out of range)

# paths./api/ask.post.responses.429.description
Rate limit exceeded. Session path: 20/day reset at midnight UTC. Anonymous path: 2/day, 48h TTL.

# paths./api/ask.post.responses.429.headers.Retry-After.description
RFC 7231 -- seconds until the client may retry.

# paths./api/articles.get.description
Returns a paginated list of published, non-retracted research articles from the RRM Academy library. Public, unauthenticated. Rate limited to 30 requests per minute per IP. Responses are edge-cached for 1 hour.

# paths./api/articles.get.parameters[].description
Page number (1-based). Default: 1. Max: 350.

# paths./api/articles.get.responses.400.description
Invalid pagination parameters or malformed `ids` query.

# paths./api/articles.get.responses.429.headers.Retry-After.description
RFC 7231 -- seconds until the client may retry.

# paths./api/articles/bulk.get.description
Returns up to 50 published library articles by ID in a single call. Comma-separated IDs in the `ids` query parameter. Missing IDs are returned in `not_found[]` instead of producing a 404. Shares the 30 req/min IP budget with /api/articles.

# paths./api/articles/bulk.get.parameters[].description
Comma-separated list of article IDs to fetch (max 50, deduped, order preserved).

# paths./api/articles/bulk.get.responses.200.description
Bulk article results. Missing IDs appear in not_found[].

# paths./api/articles/bulk.get.responses.429.description
Rate limited (30 req/min per IP, shared with /api/articles)

# paths./api/articles/bulk.get.responses.429.headers.Retry-After.description
RFC 7231 -- seconds until the client may retry.

# paths./api/bulk.get.summary
Bulk-fetch articles by ID (top-level alias)

# paths./api/bulk.get.description
Top-level alias for /api/articles/bulk. Identical behavior. Shares the 30 req/min IP budget. Exists so URL-pattern scanners recognize the batch endpoint at /api/bulk.

# paths./api/bulk.get.parameters[].description
Comma-separated list of article IDs to bulk-fetch (max 50, deduped, order preserved). Missing IDs returned in not_found[] instead of producing a 404.

# paths./api/bulk.get.responses.429.headers.Retry-After.description
RFC 7231 -- seconds until the client may retry.

# paths./api/ask/sandbox.post.summary
Canned sandbox response for integration testing

# paths./api/ask/sandbox.post.description
Returns a canned response with no LLM invocation, no auth, and no rate limit. Accepts the same JSON shape as /api/ask but ignores the body. Use to validate that your client correctly parses the AskResponse-shaped reply before integrating with the live /api/ask endpoint.

# paths./api/ask/sandbox.post.requestBody.content.application/json.schema.properties.message.description
Ignored. Included for structural compatibility with /api/ask.

# paths./api/ask/sandbox.post.responses.200.description
Canned sandbox response. Shape matches SandboxAskResponse.

# paths./api/ask/sandbox.post.responses.200.content.text/event-stream.schema.description
SSE stream when Accept: text/event-stream. Two events: `data: <SandboxAskResponse JSON>` then `data: [DONE]`

# paths./mcp.post.description
Model Context Protocol JSON-RPC 2.0 entry point. Dispatches to one of five tools: search, check_guardrails, check_facts, get_article, find_related. Use the MCP client libraries (@modelcontextprotocol/sdk) for idiomatic access rather than raw HTTP.

# paths./mcp.post.responses.429.headers.Retry-After.description
RFC 7231 -- seconds until the client may retry.

# paths./health.get.description
Returns 200 when the MCP server is reachable. Unauthenticated.

# paths./api/contact/submit.post.requestBody.content.application/json.schema.description
See functions/api/contact/submit.js for the request schema.

# paths./api/newsletter/subscribe.post.summary
Subscribe an email address to the newsletter

# paths./api/community/posts.post.requestBody.content.application/json.schema.description
See functions/api/community/posts.js for the request schema.

# paths./api/community/posts.patch.summary
Edit a community post the caller authored

# paths./api/community/posts.patch.requestBody.content.application/json.schema.description
See functions/api/community/posts.js for the request schema.

# paths./api/community/posts.delete.summary
Delete a community post the caller authored

# paths./api/saved.post.summary
Save a library article to the caller's collection

# paths./api/saved.post.requestBody.content.application/json.schema.description
See functions/api/saved.js for the request schema.

# paths./api/saved.delete.summary
Remove a library article from the caller's collection

# components.parameters.IdempotencyKey.description
Opaque client-generated identifier (recommend 36-character UUID) that lets the server detect retried requests on this mutation endpoint and return the cached prior response instead of re-running side effects. Implements the IETF idempotency-header draft (https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/). Constraints: 16-128 printable ASCII characters. The server returns the same status, body, and Idempotency-Replayed: true on a successful match. If the same key is reused with a different request body, the server returns 422 with error=idempotency-mismatch. Keys expire after 24 hours. Idempotency is opt-in: requests without this header behave exactly as before.

# components.headers.RateLimitLimit.description
RFC 9598 (draft-ietf-httpapi-ratelimit-headers) -- total requests allowed in the current window.

# components.headers.RateLimitRemaining.description
RFC 9598 -- requests remaining in the current window. Agents should self-throttle when this drops to 0 rather than retrying through a 429.

# components.headers.RateLimitReset.description
RFC 9598 -- seconds until the current window resets.

# components.responses.IdempotencyMismatch.description
The Idempotency-Key was previously used with a different request body. Use a new key for a new request.

# components.responses.IdempotencyMismatch.content.application/json.example.message
This Idempotency-Key was used with a different request body. Use a new key for a new request.

# components.responses.InvalidIdempotencyKey.description
Idempotency-Key header is malformed. Must be 16-128 printable ASCII characters.

# components.responses.InvalidIdempotencyKey.content.application/json.example.message
Idempotency-Key must be 16-128 printable ASCII characters (RFC IETF idempotency-header draft).

# components.schemas.JsonRpcRequest.properties.params.description
For tools/call: { name: string, arguments: object }. Tool names: search, check_guardrails, check_facts, get_article, find_related.

# components.schemas.AskResponse.properties.answer.description
AI-generated answer scoped to RRM Library content

# components.schemas.AskResponse.properties.fallback.description
true when no relevant library content was found and a fallback message is returned

# components.schemas.BulkArticleResponse.description
Returned by GET /api/articles/bulk. Results for found IDs plus a not_found[] list for missing ones.

# components.schemas.BulkArticleResponse.properties.not_found.items.description
Requested ID that did not resolve to a published article

# components.schemas.HealthStatus.description
Health probe response. Returns the literal string `ok` when the worker is reachable.

# components.schemas.StripeWebhookEvent.description
Subset of the Stripe Event object delivered to /api/stripe-webhook. Full schema: https://docs.stripe.com/api/events/object.

# components.schemas.StripeWebhookEvent.properties.data.description
Event payload. Shape varies by event type.

# components.schemas.SandboxAskResponse.description
Returned by /api/ask when ?mode=sandbox is set. Canned response, does not consume rate-limit budget or invoke the upstream LLM.

# components.schemas.SandboxAskResponse.properties.answer.example
Sandbox response. /api/ask returns answers grounded in the RRM Library.