How to add real citations to an AI chatbot

Published September 26, 2026

Ask a general-purpose language model for sources and it will often hand you references that look perfect — real journals, plausible authors, a DOI — and don't exist. This is one of the most common complaints about AI research and writing tools, and it is a retrieval problem, not a prompting problem: the model is generating citations from memory instead of looking them up.

The fix is to make the model retrieve first, cite second. This post shows a pattern you can build in an afternoon using a paper search API.

The pattern

  1. Extract the claim. Take the sentence or question that needs support.
  2. Search a real index. Run a query for it and get back actual paper records.
  3. Let the model write only from what came back. Give it the returned titles, authors, years, DOIs and abstracts as context, and instruct it to cite only those.
  4. Verify before showing. Check that every citation in the answer maps to a record you retrieved; drop or flag any that don't.

Step 2 with OpenAbstracts

import requests

def find_sources(claim: str, limit: int = 5):
    r = requests.get(
        "https://openabstracts.com/api/references-search",
        params={"q": claim, "limit": limit},
        headers={"x-api-key": "isk_live_YOUR_KEY"},
        timeout=30,
    )
    r.raise_for_status()
    return r.json()["references"]

sources = find_sources("day-of-the-week effect African stock markets")
for s in sources:
    print(s["year"], s["title"], "|", s["journal"], "|", s["doi"])

Each record includes title, authors, year, journal, doi, abstract and links to the PDF and publisher page, which is everything a formatted citation needs.

Step 3: constrain the model

context = "\n\n".join(
    f"[{i+1}] {s['title']} — {', '.join(s['authors'][:3])} ({s['year']}). "
    f"{s['journal']}. DOI: {s['doi']}\nAbstract: {s['abstract'][:600]}"
    for i, s in enumerate(sources)
)

prompt = f"""Answer using ONLY the sources below. Cite them as [1], [2], etc.
If the sources don't support an answer, say so. Never cite anything not listed.

SOURCES:
{context}

QUESTION: {question}"""

Step 4: verify

Parse the citation markers in the model's answer and confirm each index exists in sources. If the model cites [7] and you supplied five sources, reject the answer and retry. Because you control the source list, "does this citation exist?" becomes a lookup instead of a judgement call.

When nothing is found

Design for the empty case. If the search returns no results, tell the user you couldn't find a source rather than letting the model improvise. On OpenAbstracts a search with no results is also never billed, so honest "not found" answers cost nothing.

Use MCP instead of writing glue code

If your chatbot runs on Claude or another MCP-capable assistant, you can skip the plumbing: connect the OpenAbstracts MCP server and the assistant calls search_papers itself. Add the citation Skill to instruct it never to cite a paper it hasn't searched for.

Limits to be honest about

A search index reduces fabricated references; it doesn't guarantee the model represents a paper's findings correctly. Show the abstract or a link to the source next to each citation so readers can check, and keep a human in the loop for anything high-stakes.

Ready to try it? You get 100 free searches every month — see the quickstart.