---
title: "SF-12 AI-grounded research question (PR1 dry-run substrate)"
method: POST
path: "/api/ai/research-question"
tags: ["AI"]
---

# SF-12 AI-grounded research question (PR1 dry-run substrate)

`POST /api/ai/research-question`

## Request

- Base URL: `https://www.oxfordledge.com` (the document declares no server; this is the origin it was published from)
- URL: `https://www.oxfordledge.com/api/ai/research-question`
- Auth: none declared

POST /api/ai/research-question — SF-12 PR1 substrate.

Request body (JSON):
  {
    "question": "<user question>",
    "ticker": "AAPL",
    "mode": "literacy"|"interpretation"|"comparative"|"transcript_compare",
    "dry_run": true                # PR1: must be true
  }

PR1 behavior:
  - `dry_run=True` (the only allowed value): build retrieval
    candidates via A2 RAG, return them as Citation entries with
    `answer="[DRY RUN — PR1 substrate]"`, `cost_usd=0.0`,
    `model_used="dry-run"`. No LLM call. **`dry_run` suppresses
    the LLM, not the retrieval: the question is embedded by Voyage
    AI (US) on this path too. See the module header.**
  - `dry_run=False`: return 501 with `{"detail": "live LLM ships in PR2"}`.
    Explicit refusal — never silently 200 with an empty body.
  - Advisory-shaped questions (per `_is_advisory()` — the COUNSEL
    F7 BLOCKING denylist shared with /api/ai/ask) are refused
    with 400 before any retrieval. The refusal payload echoes
    the advisory-refusal contract used by the existing Ask-AI
    surface.

## Response `200`

Successful Response

- ResearchQuestionResponse: `POST /api/ai/research-question` response — SF-12 PR1 wire contract. All listed fields are REQUIRED per CLAUDE.md F4.3 mandate. The FE research panel reads every one of them: `answer` (body), `citations` (footnote cards), `mode` + `ticker` (echo for re-issue), `cost_usd` + `model_used` (transparency + ops visibility), `advice_disclosure_footer` (COUNSEL F1 BLOCKING — verbatim AI_RESEARCH_DISCLOSURE literal), `generated_at` (cache-busting), `dry_run` (PR1 always True; PR2 flips to False on the live path). PR1 dry-run shape: `answer="[DRY RUN — PR1 substrate]"`, `cost_usd=0.0`, `model_used="dry-run"`, `dry_run=True`, citations is the retrieval-candidate list (zero-or-more) WITHOUT any LLM invocation. PR2 will flip `dry_run=False`, populate the answer body, set `cost_usd` to the actual Haiku/Sonnet spend, and set `model_used` to the model name (`claude-3-5-haiku-20241022` for the 3 extractive modes; `claude-3-5-sonnet-20241022` for `transcript_compare`).
  - `answer` string, required: The AI-generated answer body. PR1 dry-run: '[DRY RUN — PR1 substrate]'.
  - `citations` Citation[], required: Citation list. PR2+ requires ≥1; PR1 dry-run may surface 0+ retrieval candidates.
    - `source_type` 'form4' | 'sf5_endpoint' | 'market_data' | 'news', required: SF-12 citation source-type enum (R2, 2026-05-27). Four sources — three primary-source paths + news: - `form4` — Form 4 insider transaction filings (raw SEC). - `sf5_endpoint` — Oxford Ledge SF-5 interpretation layer endpoints (`/api/insiders/<ticker>/cluster`, `/api/13f/...`). - `market_data` — Price/financial figures from local DB (`/api/data/<TICKER>?fields=...`). - `news` — News-articles corpus (R2 ratified 4th type; gated by the news-citation-gate substrate landing in PR2 per B2 — pre-shipgate row refusal + verified_scope filter + "Reported by" attribution).
    - `url` string, required: Resolvable URL (SEC filing, internal endpoint, news article).
    - `label` string, required: Human-readable citation card text.
    - `excerpt` string, nullable: The specific quote/value the answer cites. None for figure-only citations.
  - `ticker` string, required: Uppercase ticker echo of the request param.
  - `mode` 'literacy' | 'interpretation' | 'comparative' | 'transcript_compare', required: SF-12 answer-mode enum (R1 + R4 ratifications, 2026-05-27). Four modes — three extractive (PR2, Haiku, substring+cosine verifier) + one delta (PR2.5, Sonnet, cross-quarter ≥2-citation + LLM-judge): - `literacy` — "What does this number mean?" (PROFESSOR financial-literacy shape; pairs with the mastery layer). - `interpretation` — "Why is X elevated/dropping?" (SF-5 conviction + cluster output shape). - `comparative` — "Compare across tickers" (pulls multiple SF-5 outputs into one answer). - `transcript_compare` — R1 headline mode: "What did management say differently vs last 3 quarters?" Cross-quarter framing delta; ships in PR2.5 only.
  - `cost_usd` number: LLM spend for this call in USD. PR1 dry-run: 0.0. Mode-weighted in PR4 (transcript_compare counts 4x).
  - `model_used` string: Model identifier (e.g. claude-3-5-haiku-20241022). PR1 dry-run: 'dry-run'.
  - `advice_disclosure_footer` string, required: Mandatory AI_RESEARCH_DISCLOSURE literal (COUNSEL F1 BLOCKING).
  - `generated_at` string, required: ISO8601 timestamp when the response was generated.
  - `dry_run` boolean, required: True in PR1 (the only allowed path); PR2 flips to False on live.

## Other responses

- `400`: Bad Request
- `401`: Unauthorized
- `402`: Payment Required
- `429`: Too Many Requests
- `500`: Internal Server Error
- `501`: Not Implemented

---

[API](https://skmtc.dev/oxfordledge/apis/oxford-ledge-api.md) · [All operations](https://skmtc.dev/oxfordledge/apis/oxford-ledge-api/llms.txt) · [OpenAPI document](https://skmtc.dev/oxfordledge/apis/oxford-ledge-api/revisions/e832a1260f0b?raw)
