How it works
An EduGenAI Extension is a function-calling tool: you give EduGenAI an HTTP endpoint and a function definition, and its assistant calls that endpoint (as a JSON request) whenever it needs to. This citation checker splits cleanly across the two sides:
| Step | Who does it |
|---|---|
| Read the paper, pull out the reference list and the sentences that cite each source | EduGenAI's assistant β the paper is already in its chat context |
| Look each reference up in OpenAlex; check title, authors, year, journal, DOI, volume, issue, pages; return the abstract | This extension endpoint β deterministic, no LLM |
| Compare the citing sentence to the abstract and flag misquotes | EduGenAI's assistant, using the abstract the endpoint returned |
Before you start
You need this app deployed at a public HTTPS URL β EduGenAI calls it server-to-server. Everywhere below, replace https://YOUR-DEPLOYMENT-HOST with your deployment's address.
Step by step
-
Open the Extension builder
In EduGenAI, create a new Extension (the panel with Name, Short description, Headers, and Functions).
-
Name and describe it
Name
OpenAlex Citation Verifier
Short description
Verifies a paper's references against OpenAlex: checks that each work exists and that the printed title, authors, year, journal, DOI and pages match, and returns the abstract.
Detail description β this is the instruction EduGenAI's assistant follows. Paste it verbatim:
Use this extension to fact-check the reference list of an uploaded paper. STEP 1 - EXTRACT. Read the document. For every entry in the reference list, pull out: title, the full list of author names in order, whether it ends in "et al.", year, DOI (only if printed), journal/venue, volume, issue, and page range. Also find the sentence(s) in the body where each source is cited, with one sentence of context on each side. Keep those citation sentences in your own working notes for Step 3 - do NOT send them to the function. STEP 2 - VERIFY. Call the "verify_references" function once, passing every reference. For each one it returns: "badge" and "severity" (0-100), "status" (found / fuzzy / not_found / lookup_failed), "mismatched_fields", a "field_check" comparing each printed detail to OpenAlex (with reference_value vs openalex_value), the matched "work" (authors, year, venue, doi, url) and its "abstract", and - for FUZZY matches only, where "work" is null and "field_check" is empty - a "candidates" array of the closest works (each with title, authors, year, venue, doi, url). STEP 3 - JUDGE MISQUOTES. For each reference whose status is "found", compare the citation sentence(s) you kept in Step 1 with the returned "abstract". If the student uses the source as though it were about a different topic than the abstract shows (e.g. the abstract is about macroeconomic productivity but the student cites it for labour productivity specifically), that is a MISQUOTE. Otherwise it is consistent. If there is no abstract, or the reference is never cited in the body, the misquote check is "unclear". Never invent a verdict. STEP 4 - PRESENT (follow this format exactly). The user must NEVER see the raw JSON from the function - it is a machine interface. Convert it into ONE readable Markdown table, one row per reference, sorted by DESCENDING severity. Use the function's "severity" value; if you judge a reference to be a misquote MISMATCH, raise its severity to AT LEAST 80 (keep the function's number if it is already higher). Columns: | # | Flag | Reference (as printed) | Status | Metadata issues | Misquote | What the paper is about | - Flag: π΅ if status is lookup_failed (it could not be checked); otherwise π΄ if severity >= 70, π if 45-69, π’ otherwise. - Status: the "badge" value with an emoji - π΄ Potential hallucination (not_found), π Fuzzy match (fuzzy), π΅ Lookup failed (found no result to compare), π’ Verified (found). - Metadata issues: if "mismatched_fields" is empty, write "-". Otherwise, for each mismatched field take its reference_value and openalex_value from "field_check" and write: field: "printed" -> "OpenAlex". Put **authors** in bold when authors is one of them. (For a fuzzy row there is no field_check yet - write "-".) - Misquote: π΄ Mismatch / π Likely mismatch / π’ Consistent / β Unclear, plus a 6-10 word reason. (Only "found" rows have a misquote; for others write "-".) - What the paper is about: one short clause summarising the "work" abstract, or "-" if none. After the table, add a "Details" section covering only the π΄ and π rows: - For a FOUND row: the matched title as a link to its "work.url", the field-by-field comparison, and one sentence on the misquote if any. - For a FUZZY row: "work" is null - instead list the entries in "candidates" (title as a link to each "url", with authors and year) and ask the user to confirm which, if any, is the intended source. Do NOT present a fuzzy row as verified. End with one summary line: "N references - X verified, Y fuzzy, Z potential hallucinations, plus any metadata/misquote flags." Never invent a DOI, author, year, or verdict the function did not return. If "status" is "lookup_failed", say the reference could not be checked - do NOT call it a hallucination.
-
Add the Header
In the Headers section, add one header so the endpoint receives JSON:
Key Value Content-Typeapplication/jsonOptional β OpenAlex Premium: if you have an OpenAlex Premium key and want higher rate limits, add a second header
X-OpenAlex-Keyand paste the key as its value. Use the Secure header values option so it is stored in Azure Key Vault, not in plain text. -
Add the function
Under Functions, add a function that points at the batch endpoint.
Method & URL
POST https://YOUR-DEPLOYMENT-HOST/api/verify_batch
Function definition (name, description, JSON parameters)
{ "name": "verify_references", "description": "Verify a list of bibliographic references against OpenAlex. For each reference, returns whether the work exists (found / fuzzy / not_found), a field-by-field comparison of the printed metadata (title, authors, year, journal, DOI, volume, issue, pages) against the real record, and the abstract of the matched work.", "parameters": { "type": "object", "properties": { "references": { "type": "array", "description": "Every reference in the paper's bibliography.", "items": { "type": "object", "properties": { "title": { "type": "string", "description": "The work's title." }, "authors": { "type": "array", "items": { "type": "string" }, "description": "Every author name printed, in order, e.g. 'Smith, J.'." }, "et_al": { "type": "boolean", "description": "True if the reference abbreviates the author list with 'et al.'." }, "year": { "type": "integer" }, "doi": { "type": "string", "description": "Only if printed in the reference." }, "journal": { "type": "string", "description": "Journal or venue name." }, "volume": { "type": "string" }, "issue": { "type": "string" }, "pages": { "type": "string", "description": "Page range as printed, e.g. '123-145'." } }, "required": ["title"] } } }, "required": ["references"] } } -
Submit and test
Click Submit. Then, in an EduGenAI chat, upload a paper (or paste its reference list) and ask: βCheck the citations in this paper against OpenAlex.β The assistant will call your endpoint and return the verified table.
What the endpoint returns
Example request EduGenAI sends to /api/verify_batch:
{
"references": [
{
"title": "Highly accurate protein structure prediction with AlphaFold",
"authors": ["Smith, J.", "Jones, B."],
"et_al": false,
"year": 2019,
"journal": "Science",
"pages": "100-110"
}
]
}Example response (abbreviated) β note the wrong authors, year, and journal are caught, and the abstract is returned for the misquote step:
{
"count": 1,
"results": [
{
"index": 1,
"status": "found",
"badge": "Verified",
"severity": 85,
"priority": "Review",
"mismatched_fields": ["authors", "year", "journal"],
"work": {
"title": "Highly accurate protein structure prediction with AlphaFold",
"authors": ["John Jumper", "Richard Evans", "..."],
"year": 2021,
"venue": "Nature",
"doi": "10.1038/s41586-021-03819-2",
"url": "https://doi.org/10.1038/s41586-021-03819-2",
"abstract": "Proteins are essential to life ... (returned for the misquote check)"
},
"field_mismatch_count": 3,
"field_check": [
{ "field": "title", "status": "match" },
{ "field": "authors", "status": "mismatch", "reference_value": "Smith, J., Jones, B.", "openalex_value": "John Jumper, Richard Evans, ...", "detail": "Not on the record: Smith, J., Jones, B." },
{ "field": "year", "status": "mismatch", "reference_value": 2019, "openalex_value": 2021 },
{ "field": "journal", "status": "mismatch", "reference_value": "Science", "openalex_value": "Nature" }
],
"notes": ["Metadata mismatch on: authors, year, journal. The work exists but the citation details differ."]
}
]
}POST /api/verify β same fields, but without the outer references array β if you prefer one call per reference. The batch endpoint is recommended for a whole bibliography (one round trip, kinder to OpenAlex's rate limits).From JSON to a readable table β what you'll see in chat
You never look at that JSON. It is the machine interface between EduGenAI and the endpoint. The Detail description you pasted in Step 2 tells EduGenAI's assistant to convert it (STEP 4) into a severity-sorted table with flags, scores, and the mismatches spelled out β mirroring how this site presents results. A typical chat answer looks like:
| # | Flag | Reference (as printed) | Status | Metadata issues | Misquote | What the paper is about |
|---|---|---|---|---|---|---|
| 3 | π΄ | Zorblatt, Q. (2021). Quantum blockchain effects on medieval cheese tradingβ¦ | π΄ Potential hallucination | β | β | β |
| 2 | π΄ | Smith, J. & Jones, B. (2019). Highly accurate protein structure predictionβ¦ Science, 100-110. | π’ Verified | authors: "Smith, Jones" β "Jumper, Evansβ¦" Β· year: 2019 β 2021 Β· journal: Science β Nature | π΄ Mismatch β cited for labour productivity; paper is protein-structure AI | Deep-learning protein structure prediction |
| 1 | π’ | Vaswani, A. et al. (2017). Attention is all you need. NeurIPS. | π’ Verified | β | π’ Consistent | Attention-only sequence model (Transformer) |
β¦followed by a Details section for the flagged rows and a one-line summary count. The assistant does this conversion because the Detail description explicitly says the user must never see the raw JSON.
badge, severity, priority, mismatched_fields) precisely so the assistant only has to lay them out, not compute them.What data passes through your server?
Because the extension calls your deployment, it helps to be precise about what actually travels there β and what does not.
| Data | Where it goes |
|---|---|
| The paper's full text, the student's name, and the citing sentences | Stay inside EduGenAI. The assistant keeps the citing sentences for its own misquote reasoning; they are never sent to your endpoint. |
| Reference metadata only β title, authors, year, DOI, journal, volume, issue, pages | Sent to your /api/verify_batch endpoint, and (titles/DOIs) on to OpenAlex for the lookup. |
| Any LLM key | Not involved server-side at all β the reasoning runs on EduGenAI's model. |
One caveat about the OpenAlex Premium key: it is used per-request, never stored, and scrubbed from any error message this app returns. But when your endpoint calls OpenAlex, the key travels as a query parameter on the outbound URL β so if you run behind an egress proxy that logs full outbound URLs, it could appear there. If that matters, don't set a Premium key (the tool works fine without one) or use a proxy that redacts query strings.
Notes & limits
- No LLM key is stored or used by the extension; OpenAlex is free and needs no key.
- The metadata check compares against OpenAlex's canonical record. Occasionally OpenAlex's year differs from the printed year (e.g. online-first vs issue year) β treat a lone year mismatch as a prompt to double-check, not a verdict.
- The misquote judgement is based on the abstract only, so a claim supported by the full text but not the abstract may read as uncertain. These are leads for a human reviewer.
- Batch requests are capped at 200 references.