From 698d6cd2810502fe17e5e71a117dcfc08c26a96a Mon Sep 17 00:00:00 2001 From: Priyansh Khodiyar Date: Fri, 15 May 2026 18:46:29 +0530 Subject: [PATCH] Add Vaquill as an alternative legal-research provider Sits alongside the existing LegalDataHunter integration without touching it. Both providers can be enabled simultaneously; the frontend picks which to call. New routes: - POST /vaquill/ask: AI-grounded legal Q&A across US primary law (Constitution, USC, CFR, Federal Rules of Procedure, all 50 state statute codes, state constitutions, state court rules, Executive Orders since 2015) plus Indian case law (31M+ judgments) and citation-graph traversal. - POST /vaquill/statutes/search: hybrid semantic + keyword search across US primary law (with corpus-type and state filters) and Indian acts. Wiring: - backend/src/routes/vaquill.ts: new proxy router, env-gated on VAQUILL_API_KEY. - backend/src/index.ts: import, app.use(/vaquill), integration-status line. - backend/.env.example: documented VAQUILL_API_KEY alongside LEGAL_DATA_HUNTER_API_KEY. - README.md: tech stack + env-var docs + third-party-keys list updated. Backwards-compatible: zero changes to legalData.ts. Existing LDH deployments keep working unchanged. Operators who want Vaquill add VAQUILL_API_KEY; operators who want both set both. --- README.md | 26 ++-- open-specter-main/backend/.env.example | 13 ++ open-specter-main/backend/src/index.ts | 3 + .../backend/src/routes/vaquill.ts | 125 ++++++++++++++++++ 4 files changed, 159 insertions(+), 8 deletions(-) create mode 100644 open-specter-main/backend/src/routes/vaquill.ts diff --git a/README.md b/README.md index b734770..199ecee 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Maintained by **Quantera.ai**. | Database/Auth | Supabase Auth + Postgres | | Storage | Cloudflare R2 / S3-compatible storage, with local fallback for development | | AI Providers | Gemini, Anthropic, OpenRouter-compatible models | -| Legal research | [LegalDataHunter](https://legaldatahunter.com) — case law & legislation across 178 jurisdictions | +| Legal research | [LegalDataHunter](https://legaldatahunter.com) (case law & legislation across 178 jurisdictions) and/or [Vaquill](https://www.vaquill.ai/legal-api) (AI Q&A across US primary law and Indian case law) | | Document tooling | LibreOffice for DOC/DOCX conversion | --- @@ -105,13 +105,23 @@ ANTHROPIC_API_KEY=your-anthropic-key OPENROUTER_API_KEY=your-openrouter-key RESEND_API_KEY=your-resend-key -# LegalDataHunter — required for the "Sources" panel and inline legal-research -# citations in the assistant chat. This is a paid third-party API. You MUST use -# YOUR OWN key — usage is billed against the key holder's account, and Open -# Specter ships no fallback. Sign up at https://legaldatahunter.com to get one. -# If this variable is unset, the Sources feature is silently disabled and the -# rest of the app keeps working. +# Legal-research providers. Set whichever one(s) you want to enable; both can +# be enabled simultaneously. The frontend chooses which to call. If neither is +# set, the Sources feature and inline legal-research citations are silently +# disabled and the rest of the app keeps working. +# +# Each is a paid third-party API. You MUST use YOUR OWN key. Usage is billed +# against the key holder's account; Open Specter ships no fallback. + +# LegalDataHunter: case law and legislation across 178 jurisdictions. +# Sign up at https://legaldatahunter.com. LEGAL_DATA_HUNTER_API_KEY=your-legaldatahunter-key + +# Vaquill: AI-grounded Q&A across US primary law (Constitution, USC, CFR, +# Federal Rules, 50-state codes, Executive Orders since 2015) plus Indian +# case law (31M+ judgments) and citation-graph traversal. Sign up at +# https://www.vaquill.ai/legal-api. Key format: vq_key_... +VAQUILL_API_KEY=vq_key_your_vaquill_key ``` ### Frontend @@ -333,7 +343,7 @@ deployments. ## Third-party API keys are your responsibility Open Specter integrates with paid third-party services (Supabase, Cloudflare -R2, Gemini, Anthropic, OpenRouter, Resend, LegalDataHunter). Open Specter +R2, Gemini, Anthropic, OpenRouter, Resend, LegalDataHunter, Vaquill). Open Specter ships **no shared/upstream fallback** for any of them — every self-host must provide its own credentials in `backend/.env`. Usage of each integration is billed against the key holder's account; the project maintainers cannot be diff --git a/open-specter-main/backend/.env.example b/open-specter-main/backend/.env.example index 0b17d74..e2d288d 100644 --- a/open-specter-main/backend/.env.example +++ b/open-specter-main/backend/.env.example @@ -12,3 +12,16 @@ GEMINI_API_KEY=your-gemini-key ANTHROPIC_API_KEY=your-anthropic-key OPENROUTER_API_KEY=your-openrouter-key RESEND_API_KEY=your-resend-key + +# Legal-research providers. Set whichever one(s) you want to enable. +# Both can be enabled simultaneously; the frontend chooses which to call. + +# LegalDataHunter: case law and legislation across 178 jurisdictions. Paid API. +# Sign up at https://legaldatahunter.com. +LEGAL_DATA_HUNTER_API_KEY=your-legaldatahunter-key + +# Vaquill: AI-grounded Q&A across US primary law (Constitution, USC, CFR, +# Federal Rules, 50-state codes, Executive Orders since 2015) plus Indian +# case law and citation-graph traversal. Paid API. Sign up at +# https://www.vaquill.ai/legal-api. +VAQUILL_API_KEY=vq_key_your_vaquill_key diff --git a/open-specter-main/backend/src/index.ts b/open-specter-main/backend/src/index.ts index 306f9f2..ac8df4d 100644 --- a/open-specter-main/backend/src/index.ts +++ b/open-specter-main/backend/src/index.ts @@ -11,6 +11,7 @@ import { userRouter } from "./routes/user"; import { downloadsRouter } from "./routes/downloads"; import { activityRouter } from "./routes/activity"; import { legalDataRouter } from "./routes/legalData"; +import { vaquillRouter } from "./routes/vaquill"; const app = express(); const PORT = process.env.PORT ?? 3001; @@ -35,6 +36,7 @@ app.use("/users", userRouter); app.use("/download", downloadsRouter); app.use("/activity", activityRouter); app.use("/legal-data", legalDataRouter); +app.use("/vaquill", vaquillRouter); app.get("/health", (_req, res) => res.json({ ok: true })); @@ -61,6 +63,7 @@ function logIntegrationStatus(): void { console.log(` Resend (email) : ${status(has(process.env.RESEND_API_KEY))}`); console.log(` Cloudflare R2 : ${status(has(process.env.R2_ENDPOINT_URL) && has(process.env.R2_ACCESS_KEY_ID))}`); console.log(` LegalDataHunter : ${status(has(process.env.LEGAL_DATA_HUNTER_API_KEY))}`); + console.log(` Vaquill : ${status(has(process.env.VAQUILL_API_KEY))}`); if (!has(process.env.LEGAL_DATA_HUNTER_API_KEY)) { console.warn( " ⚠ LEGAL_DATA_HUNTER_API_KEY is not set. The 'Sources' panel and inline\n" + diff --git a/open-specter-main/backend/src/routes/vaquill.ts b/open-specter-main/backend/src/routes/vaquill.ts new file mode 100644 index 0000000..d902bd7 --- /dev/null +++ b/open-specter-main/backend/src/routes/vaquill.ts @@ -0,0 +1,125 @@ +import { Router } from "express"; +import { requireAuth } from "../middleware/auth"; + +export const vaquillRouter = Router(); + +const VAQUILL_BASE = "https://api.vaquill.ai/api/v1"; + +/** + * Vaquill (https://www.vaquill.ai/legal-api) is an alternative legal-research + * provider. It exposes AI-grounded Q&A across US primary law (Constitution, + * USC, CFR, Federal Rules of Procedure, all 50 state statute codes, state + * constitutions, state court rules, Executive Orders since 2015) plus Indian + * case law (31M+ judgments) and citation-graph traversal. + * + * This router sits alongside legalData.ts (LegalDataHunter) so operators can + * pick whichever provider suits their corpus needs. Both can be enabled + * simultaneously; the frontend chooses which to call. + * + * Configure VAQUILL_API_KEY in backend/.env. Key format: vq_key_... + */ + +function resolveVaquillKey(): string | null { + const key = process.env.VAQUILL_API_KEY; + return typeof key === "string" && key.trim() ? key.trim() : null; +} + +/** + * POST /vaquill/ask: AI-grounded legal Q&A. + * + * Body: + * - question (required, string) + * - countryCode (optional, "US" | "IN", defaults upstream) + * - usState (optional, e.g. "tx") + * - mode (optional, "standard" | "deep") + */ +vaquillRouter.post("/ask", requireAuth, async (req, res) => { + const apiKey = resolveVaquillKey(); + if (!apiKey) { + return void res.status(400).json({ + detail: "Vaquill API key is not configured.", + }); + } + + const { question, countryCode, usState, mode } = req.body ?? {}; + if (typeof question !== "string" || !question.trim()) { + return void res + .status(400) + .json({ detail: "`question` is required." }); + } + + const payload: Record = { question: question.trim() }; + if (countryCode) payload.countryCode = countryCode; + if (usState) payload.usState = usState; + if (mode) payload.mode = mode; + + try { + const upstream = await fetch(`${VAQUILL_BASE}/ask`, { + method: "POST", + headers: { + Authorization: `Bearer ${apiKey}`, + "content-type": "application/json", + }, + body: JSON.stringify(payload), + }); + const body = await upstream.text(); + res.status(upstream.status) + .setHeader("content-type", "application/json") + .send(body); + } catch (err) { + console.error("[vaquill] ask upstream error", err); + res.status(502).json({ + detail: "Vaquill upstream failure.", + }); + } +}); + +/** + * POST /vaquill/statutes/search: Search US primary law and Indian acts. + * + * Body: + * - q (required, string) + * - corpusType (optional, one of: USC, CFR, STATE, CONSTITUTION, FEDERAL_RULES, STATE_CONSTITUTION, STATE_RULES, EXECUTIVE_ACTION) + * - state (optional, ISO state code e.g. "tx" to restrict STATE-scoped corpora) + * - titleNumber (optional, integer) + * - limit (optional, integer) + */ +vaquillRouter.post("/statutes/search", requireAuth, async (req, res) => { + const apiKey = resolveVaquillKey(); + if (!apiKey) { + return void res.status(400).json({ + detail: "Vaquill API key is not configured.", + }); + } + + const { q, corpusType, state, titleNumber, limit } = req.body ?? {}; + if (typeof q !== "string" || !q.trim()) { + return void res.status(400).json({ detail: "`q` is required." }); + } + + const payload: Record = { q: q.trim() }; + if (corpusType) payload.corpusType = corpusType; + if (state) payload.state = state; + if (titleNumber) payload.titleNumber = titleNumber; + if (limit) payload.limit = limit; + + try { + const upstream = await fetch(`${VAQUILL_BASE}/statutes/search`, { + method: "POST", + headers: { + Authorization: `Bearer ${apiKey}`, + "content-type": "application/json", + }, + body: JSON.stringify(payload), + }); + const body = await upstream.text(); + res.status(upstream.status) + .setHeader("content-type", "application/json") + .send(body); + } catch (err) { + console.error("[vaquill] statutes search upstream error", err); + res.status(502).json({ + detail: "Vaquill upstream failure.", + }); + } +});