DeepCitation + Express.js
Add citation verification to any Express.js API. Three endpoints: upload, chat (proxy your LLM), and verify.
CSS not needed. Express APIs return JSON — the React components (CitationComponent) are only used if you have a separate frontend consuming these endpoints.
Install
npm install deepcitation express multer @types/multer
File Structure
src/
├── server.ts ← Express app with three routes
├── upload.ts ← prepareAttachments()
├── chat.ts ← wrapCitationPrompt() + your LLM call
└── verify.ts ← getAllCitationsFromLlmOutput() + verifyAttachment()
Setup
// server.ts
import express from "express";
import multer from "multer";
import { DeepCitation } from "deepcitation";
const app = express();
const upload = multer({ storage: multer.memoryStorage() });
const dc = new DeepCitation({ apiKey: process.env.DEEPCITATION_API_KEY! });
app.use(express.json());
Route 1: Upload Document
// POST /api/upload
app.post("/api/upload", upload.single("file"), async (req, res) => {
const file = req.file;
if (!file) return res.status(400).json({ error: "No file provided" });
const { fileDataParts, deepTextPages } = await dc.prepareAttachments([
{ file: file.buffer, filename: file.originalname },
]);
res.json({
fileDataPart: fileDataParts[0],
deepTextPages,
});
});
Route 2: Chat (Proxy Your LLM)
import { wrapCitationPrompt } from "deepcitation";
// POST /api/chat
app.post("/api/chat", async (req, res) => {
const { userMessage, deepTextPages } = req.body;
const { enhancedSystemPrompt, enhancedUserPrompt } = wrapCitationPrompt({
systemPrompt: "You are a helpful assistant that provides cited responses.",
userPrompt: userMessage,
deepTextPages,
});
// Replace with your LLM provider (e.g. gpt-5-mini, gemini-2.0-flash-lite)
const llmOutput = await callYourLLM(enhancedSystemPrompt, enhancedUserPrompt);
res.json({ llmOutput });
});
Route 3: Verify Citations
import { getAllCitationsFromLlmOutput } from "deepcitation";
// POST /api/verify
app.post("/api/verify", async (req, res) => {
const { llmOutput, attachmentId } = req.body;
// Golden rule: CitationRecord is Record<string, Citation>, NOT an array
const citations = getAllCitationsFromLlmOutput(llmOutput);
const { verifications } = await dc.verifyAttachment(attachmentId, citations);
res.json({ verifications });
});
Golden Rules
- CitationRecord is an object, not an array —
Record<string, Citation>. UseObject.keys(citations).length, not.length. - Always call
parseCitationResponse()before displaying LLM output to users —.visibleTextstrips<<<CITATION_DATA>>>markers. - Never fabricate citation URLs — only use URLs returned by the verification API.
- Keep your API key server-side — never send it to the browser.
import { parseCitationResponse } from "deepcitation";
// Before displaying to users:
const result = parseCitationResponse(llmOutput);
const cleanText = result.visibleText; // <<<CITATION_DATA>>> markers stripped
Error Handling
DeepCitation errors extend DeepCitationError with isRetryable and docUrl:
import { DeepCitationError } from "deepcitation";
app.post("/api/verify", async (req, res) => {
try {
const citations = getAllCitationsFromLlmOutput(req.body.llmOutput);
const result = await dc.verifyAttachment(req.body.attachmentId, citations);
res.json(result);
} catch (err) {
if (err instanceof DeepCitationError) {
res.status(err.statusCode ?? 500).json({
error: err.message,
retryable: err.isRetryable,
docs: err.docUrl,
});
} else {
res.status(500).json({ error: "Internal server error" });
}
}
});
Start the Server
const PORT = process.env.PORT ?? 3001;
app.listen(PORT, () => console.log(`DC API running on port ${PORT}`));
Next Steps
- API Reference — full endpoint documentation
- Error Handling — all error codes and fix steps
- Styling — if you’re building a React frontend to consume these endpoints