AI citation counts may be under-reporting, and one Google Doc shows why
· Chris Dolan
If you are counting how often AI answers cite what you published, your number may be too low - and not by a little. The mechanism is dull enough to miss and structural enough that no amount of checking makes it visible: one thing can have several addresses, and you only stored one of them.
The address you hold is whatever your tooling captured when the thing was created. The address an engine writes into an answer is whatever that engine considers the readable form. If your check is answer.contains(storedUrl), those two strings simply never meet, and the result is a zero that looks like a finding.
We hit this on Google, which is the case we can show you in full: a single Doc turned out to be reachable at seven different addresses. Six share one identifier. The seventh does not, and that one is the interesting part. Google is the worked example rather than the point - the same shape is available to any platform that gives one object more than one address, and most of them do.
The same file, six addresses that share an ID
Every address below points at the same hypothetical document. The file ID is fake; the shapes are real.
https://docs.google.com/document/d/1EXAMPLE.../edit?usp=drivesdk- what the Drive API hands back aswebViewLinkwhen your code creates the filehttps://docs.google.com/document/d/1EXAMPLE.../edit- the plain editor link, and the form Google's own API documentation uses in its exampleshttps://docs.google.com/document/d/1EXAMPLE.../view- the read-only form, and a common way an engine writes ithttps://docs.google.com/document/d/1EXAMPLE...- the bare link, with no action suffix at allhttps://docs.google.com/document/d/1EXAMPLE.../mobilebasic- server-rendered HTML, readable with no JavaScripthttps://drive.google.com/file/d/1EXAMPLE.../view- the same file addressed through Drive rather than the editor
Read down the list and the point makes itself. Six strings, no two of which contain each other, all resolving to one document. There is a seventh, and it behaves differently enough to deserve its own section.
The asymmetry is what does the damage. A publisher's stored form is whatever their tooling captured at creation time. Google's Drive API describes webViewLink as "a link for opening the file in a relevant Google editor or viewer in a browser" - a link for opening, not a canonical address, and Google never claims otherwise. An engine writing a source into an answer has no reason to reproduce your ?usp=drivesdk parameter or your /mobilebasic suffix. It writes the form a human would recognise.
The seventh address, which does not contain the ID
Publish a Doc to the web - File, Share, Publish to web - and Google issues a different kind of address: https://docs.google.com/document/d/e/2PACX-1vRExample.../pub
Note the /d/e/. What follows is not the file ID. It is a separate publish token, minted for the published copy, and it cannot be derived from the file ID by any string operation. So the rule that saves you on the first six addresses does not save you here: extract the /d/<id> fragment and this URL yields the token, which matches nothing you have stored.
That makes it the one case where a pattern cannot help. The join has to be recorded rather than computed.
The good news is that it is recorded for you, and can be read back. The Drive API's revisions resource carries publishedLink, requested as GET /drive/v3/files/{FILE_ID}/revisions?fields=revisions(id,published,publishedLink,modifiedTime).
Two details will cost you time if you skip them. publishedLink is not in the default field set, so a call that does not name it in fields returns nothing and a published file looks exactly like an unpublished one. And check Google's live discovery document rather than your client library's description of the field: at least one widely used package still describes publishedLink as "only populated for Google Sites files", while the current discovery document says "only populated for Docs Editors files" - Docs, Sheets, Slides and Drawings. The package's prose is generated, and it goes stale; the discovery document is the API, it is public, and it needs no authentication.
So: store both identifiers against one asset. The file ID catches the six, the publish token catches the seventh, and neither substitutes for the other.
The ID is the part that holds still
Google's own developer documentation is explicit about which fragment is the identity.
For Docs: "the documentId is the unique identifier for the document and it can be derived from a document's URL", shown as https://docs.google.com/document/d/DOCUMENT_ID/edit. The same page states that document IDs are stable even when the document's name changes, gives the extraction pattern /document/d/([a-zA-Z0-9-_]+), and notes that the ID corresponds to the id field on Drive's file resource.
For Sheets: the same structure and the same stability, at https://docs.google.com/spreadsheets/d/SPREADSHEET_ID/edit, with spreadsheet IDs likewise described as stable across renames.
Slides and Forms follow the pattern. The whole family addresses a file as /d/<id>, with the surrounding path describing what you want to do with it. So the ID survives every rewrite of the URL around it. Keying on it is therefore both more permissive and more precise than comparing addresses.
The failure you will not notice
Substring matching fails silently, and it fails hardest in one specific direction: when the address you stored is longer than the address that was cited.
Google Sites is the clearest case. A hub created at sites.google.com/view/acme-hub/acme-hub is stored with that page path. An engine citing the site will often write the shorter root, sites.google.com/view/acme-hub. The stored form cannot appear inside the cited one, so the hub reports "never cited" no matter how often it is named. The fix is the same in shape: key on the site slug rather than the page, since the slug identifies the site and any page on your hub is still your hub.
What to do about it
- Extract a key when you record the URL, and store it alongside. For anything matching
(docs|drive).google.com/<type>/d/<id>, capture the ID. Bound it with a slash or end-of-string so a longer path segment cannot be misread as one, and require 20-plus characters so a short segment is rejected rather than treated as an identity. - Match on the key where you have one, case-insensitively, against the answer text.
- Keep whole-URL matching where you do not. This is the part people get wrong in the other direction. A page on your own domain has no stable global ID, and a bare path is genuinely ambiguous -
/blog/seo-audit-toolexists on a hundred sites. A false citation is worse than a missed one, so ordinary pages keep the strict comparison. - Record the publish-to-web address separately. Publishing a file produces a new URL of the form
/document/d/e/2PACX-.../pub. The token after/d/e/is a publish token, not the file ID, so it will not join up with the editor form on its own. If you publish to the web, store both identifiers against the same item. - Re-run your history. This is a change to the instrument, not to the data. Every answer you have already captured can be re-checked against the new key, and any citation that was there all along will surface.
Where else this could bite
We measured Google. We have not measured the others, and this section is a description of the shape to look for rather than a set of findings.
The pattern to check for is a stable, opaque identifier sitting inside a path that varies. Wherever you find one, ask three questions:
- How many addresses reach this object? A share link, an embed link, a short link, a mobile form, a canonical form with a slug, and the same page behind a custom domain are all candidates. They are usually documented, and often only in the API reference rather than the help pages.
- Which form did I store? Almost always the one the API returned at creation, which is optimised for your code opening the file again - not for how anyone else would write it down.
- Does any form carry a different identifier? This is the one that catches people out, because the fix that works everywhere else fails here. On Google it is the published-to-web address; elsewhere it might be a short link that resolves server-side, or a syndicated copy with its own id. An identifier minted separately cannot be computed from the one you hold - it has to be recorded when it is created, or it is lost.
Video platforms, social posts, documentation sites and anything with a URL shortener are all worth auditing on those three questions. So is your own site, if it serves the same page at more than one path.
None of that is a claim about how badly any specific platform misreports. It is a claim that the question is worth asking, because on the one platform we did check, the answer was seven.
What we found, stated exactly
We hit this in our own measurement. Our citation check compared the stored address against the text of the answer, and we store Google properties as the publisher hands them back - /edit?usp=drivesdk, /mobilebasic. Neither string can appear in an answer written any other way. The count could not have come out as anything except zero. That number was a property of the comparison, not of the content.
Separately, we have seen one of our own Docs appear as a source in an AI answer. That is a single observation. It carries no rate, no timescale, and no explanation of why it happened.
The generalisable part is the method, and it is not specific to Google. Wherever a publisher holds one encoding of an identity and a citing system writes another, string equality is the wrong test. Find the stable fragment, key on that, keep the strict comparison where no stable fragment exists - and where a system mints a second identity for the same object, as publishing to the web does, record it at the moment it is created, because nothing downstream can reconstruct it.