BizGenius

Citation widget · User manual

Dual rendering: let AI read your citations

Your citation widget is drawn in the visitor's browser by JavaScript. Google runs that JavaScript, but most AI assistants (ChatGPT, Claude, Perplexity) don't, so they see empty boxes where your citations should be. Dual rendering puts the citations straight into your page HTML on your server, and the widget keeps working in the browser exactly as before.

How it works

Most AI crawlers download your page and read the HTML, without running any JavaScript. Today, that HTML contains only the empty widget boxes. With dual rendering, your server fills each box with the citations before it sends the page:

Before: what AI sees today
<div id="bizgenius-citation-widget-container-list"
     data-maxnum="10"></div>
After: citations in the HTML
<div id="bizgenius-citation-widget-container-list"
     data-maxnum="10">
  <div class="bizgenius-ssr" ...>
    Regional brain volume abnormalities in
    Lesch-Nyhan disease ... The Lancet Neurology,
    2013. doi:10.1016/s1474-4422(13)70238-2 ...

It's called dual rendering because both happen: your server prints the citations, then the widget takes over in the browser for search, filters and paging. Visitors see the same widget as before, and the citations show up about a second sooner. Every visitor, person or crawler, gets the same page.

See it live: open our demo page, then View Page Source. The paper titles are in the HTML.

Choose your setup

Which one fits depends on how your website is built. If you're not sure, ask whoever looks after your website, or ask us and we'll check.

Before you start (setups 1 and 2):
  • The widget must already be on your pages: the citation.min.js script and the bizgenius-citation-widget-container-... boxes.
  • The boxes must be in the page HTML and empty. A box added by Google Tag Manager or other JavaScript, or one with anything inside (even "Loading ..." or a comment), is left alone. Move such boxes into your page or theme template.
  • Testing on a staging site? Tell us its domain so we can allow it. Until then its boxes simply stay empty.

1. WordPress

Our plugin, BizGenius Citations for AI Search, finds the empty widget boxes on each page and fills them with your citations. It works with any theme and page builder (Elementor, WPBakery, Avada, Gutenberg, classic editor).

  1. Download the plugin.
    bizgenius-ssr.zip version 1.0.7, 8 KB
  2. Install it. In WordPress admin go to Plugins → Add New → Upload Plugin, choose bizgenius-ssr.zip, then click Install Now.
  3. Click Activate. That's it. There are no settings to fill in.
  4. Clear your page cache once if you use one (WP Rocket, LiteSpeed Cache, W3 Total Cache, WP Engine, Cloudflare). Cached pages are then saved with the citations in them.

Requirements: WordPress 5.0 or later and PHP 7.2 or later.

To remove it, deactivate and delete the plugin. Your pages go back to browser-only rendering.

2. Other PHP websites

For any website whose pages are built by PHP, such as Magento, TYPO3, Drupal, Joomla or your own code. One file, bizgenius-ssr.php, does the same job as the WordPress plugin. Keep your widget boxes and the citation.min.js script as they are.

  1. Download the file.
    bizgenius-ssr.php 20 KB
  2. Put it on your server, preferably outside the web root, for example /var/www/bizgenius-ssr.php.
  3. Load it on each page that has widget boxes. Make this the very first line of the page, before any HTML:
    <?php require_once '/var/www/bizgenius-ssr.php'; ?>
    Many pages? Load it for the whole site instead, and skip editing them. PHP has a setting, auto_prepend_file, that runs a file before every page. Set it in one of these places (one is enough):
    • Most servers: add this line to the file .user.ini in your website's root folder (create the file if it isn't there; on Magento 2 the root folder is pub/). PHP picks it up within 5 minutes.
      auto_prepend_file = "/var/www/bizgenius-ssr.php"
    • Apache running PHP as a module (mod_php), which ignores .user.ini: add this line to the .htaccess file in your root folder instead. Use it only on mod_php: on other servers it makes the site show an error.
      php_value auto_prepend_file "/var/www/bizgenius-ssr.php"
    • The whole server: your server administrator sets auto_prepend_file in php.ini (or the PHP-FPM pool file) and restarts PHP.
    Pages that already have the require_once line are fine: the file only runs once.
  4. Clear your full-page cache once if you use one (Varnish, Fastly, Magento's built-in cache, a CDN). Cached pages are then saved with the citations in them.

Requirements: PHP 7.2 or later, with the curl extension (or allow_url_fopen). Only HTML pages are touched; images, JSON and downloads pass straight through.

Optional settings

Define these in any config file your pages already load, or uncomment the lines at the top of bizgenius-ssr.php:

define('BIZGENIUS_SSR_KEY', 'YOUR_WIDGET_KEY');         // only if your widget key isn't in the page HTML
define('BIZGENIUS_SSR_CACHE_DIR', '/path/to/cache');     // a writable folder, instead of the system temp folder
define('BIZGENIUS_SSR_DISABLE', true);                   // switch it off
Frameworks that discard PHP's output buffer. A few frameworks throw away PHP's output buffers when they start, so the file never sees the page. In that case, pass the finished HTML through one function before you send it, for example in a response middleware:
$html = bizgenius_ssr_fill($html);
Calling it on a page that's already filled does nothing, so it's safe to do both.

3. Any other website: our feed (HTML or JSON)

If your website doesn't run PHP (Next.js, Nuxt, Gatsby, Astro, Adobe Experience Manager, Java, .NET, Python, Ruby ...), your developer calls one URL from your server, or at build time, and prints the result into the page. The feed comes in two formats:

Ready-made HTMLData (JSON)
Looks likeYour widget, exactlyYour own design
Where it goesInside the empty widget box, printed as it isAnywhere, in your own template
Widget script (JS)Required: paper titles open through it, and it adds search and pagingOptional
Your workPrint one piece of HTMLBuild a template and escape the text

Already showing the widget? Use ready-made HTML. Want the citations in your own design? Use data (JSON).

Fetch on the server or at build time, never in the visitor's browser. AI crawlers don't run JavaScript, so anything fetched in the browser stays invisible to them, and the feed doesn't accept browser requests from other websites anyway.

The request

GET https://www.bizgeniusapp.com/api/render.php with these parameters:

ParameterWhat to send
keyRequired. Your widget key: the content of your bizgeniusapp-key meta tag, or the key= in your citation.min.js URL. Use 1 for demo data.
domainRequired. Your website's domain, e.g. www.example.com. It must be a domain registered for your widget, even when you fetch at build time.
partRequired. Which box: list, list-top, count-total-year-month, aisummary and so on, listed in the table under Data (JSON). form (the search form) is HTML only.
formatLeave it out for ready-made HTML; json for data.
a[...]Optional. The same settings as the box's data-* attributes, without data-: a[product-name]=180, a[maxnum]=10, a[order]=publicationDate, a[show-abstract]=yes and so on. See the Widget Playground for every setting.
sourcekeywordOptional. Only if your citation.min.js URL has a sourcekeyword=... (one account shared by several brand websites): send the same value, or you get every brand's papers.
startOptional, data (JSON) only. Paging for lists: 0, then 10, 20 ...
products[]Optional, part=count-product only: up to 100 product names in one call, for listing pages.

Try it with demo data: 2 citations (HTML) · 2 citations (JSON) · citation counts (JSON)

Ready-made HTML

Leave out format, and you get the HTML the widget shows. Print it, exactly as it is, inside the matching empty widget box. Its styles only apply inside that box, so keep the box, with the same data-* settings as your a[...], and the citation.min.js script on your page. The widget then takes over in the browser as usual.

// Server side or at build time: a Next.js server component, Nuxt server route,
// Gatsby / Astro build, Cloudflare Worker, Express ...
const params = new URLSearchParams({
  key: 'YOUR_WIDGET_KEY',
  domain: 'www.example.com',          // your website's domain
  part: 'list',
  'a[maxnum]': '10',                  // the same settings as the box's data-* attributes
  'a[order]': 'publicationDate',
});
let html = '';
try {
  const res = await fetch('https://www.bizgeniusapp.com/api/render.php?' + params, {
    signal: AbortSignal.timeout(3000),  // don't hold up your page
    // Next.js: next: { revalidate: 43200 },  // keep a copy for 12 hours
  });
  if (res.status === 200) html = await res.text();   // 204 = nothing to show
} catch (e) { /* BizGenius slow or unreachable: leave the box empty */ }

// Print it as it is, inside the empty box, e.g. in JSX:
// <div id="bizgenius-citation-widget-container-list" data-maxnum="10" data-order="publicationDate"
//      dangerouslySetInnerHTML={{ __html: html }} />
import requests

html = ""
try:
    r = requests.get("https://www.bizgeniusapp.com/api/render.php", params={
        "key": "YOUR_WIDGET_KEY",
        "domain": "www.example.com",      # your website's domain
        "part": "list",
        "a[maxnum]": "10",                # the same settings as the box's data-* attributes
        "a[order]": "publicationDate",
    }, timeout=3)
    if r.status_code == 200:              # 204 = nothing to show
        html = r.text
except requests.RequestException:
    pass                                  # leave the box empty

# Print it as it is, inside the empty box (Jinja2 / Django: {{ html|safe }}):
# <div id="bizgenius-citation-widget-container-list" data-maxnum="10" data-order="publicationDate">{{ html|safe }}</div>
<?php
$html = @file_get_contents('https://www.bizgeniusapp.com/api/render.php?' . http_build_query([
    'key'    => 'YOUR_WIDGET_KEY',
    'domain' => 'www.example.com',       // your website's domain
    'part'   => 'list',
    'a'      => ['maxnum' => '10', 'order' => 'publicationDate'],   // the box's data-* settings
]), false, stream_context_create(['http' => ['timeout' => 3]]));   // '' on 204, false on failure
?>
<div id="bizgenius-citation-widget-container-list" data-maxnum="10" data-order="publicationDate"><?= $html ?: '' ?></div>
curl -G "https://www.bizgeniusapp.com/api/render.php" \
  --data-urlencode "key=YOUR_WIDGET_KEY" \
  --data-urlencode "domain=www.example.com" \
  --data-urlencode "part=list" \
  --data-urlencode "a[maxnum]=10"

A citation list (part=list) comes back like this, shortened. The ld+json part is schema.org data that tells search engines and AI assistants what each paper is:

<div class="bizgenius-ssr" data-ssr-part="list" style="display:contents">
  <div class="bizgenius-citation-list" style="width:100%;text-align:left;">
    <style> ... </style>
    ... one card per paper: title, authors, journal, year, DOI, snippet ...
  </div>
  <script type="application/ld+json">{"@context":"https://schema.org","@type":"ItemList",
    "name":"Peer-reviewed publications citing our products", ...}</script>
</div>

Data (JSON)

Add format=json to the same request and show the results with your own template.

// Server side or at build time: a Next.js server component, Nuxt server route,
// Gatsby / Astro build, Cloudflare Worker, Express ...
const params = new URLSearchParams({
  key: 'YOUR_WIDGET_KEY',
  domain: 'www.example.com',          // your website's domain
  part: 'list',
  format: 'json',
  'a[maxnum]': '10',
  'a[order]': 'publicationDate',
});
let papers = [];
try {
  const res = await fetch('https://www.bizgeniusapp.com/api/render.php?' + params, {
    signal: AbortSignal.timeout(3000),  // don't hold up your page
    // Next.js: next: { revalidate: 43200 },  // keep a copy for 12 hours
  });
  if (res.status === 200) papers = (await res.json()).papers;   // 204 = nothing to show
} catch (e) { /* BizGenius slow or unreachable: build the page without citations */ }

// Then render papers with your own template, e.g. in JSX:
// {papers.map(p => <li key={p.id}><a href={p.url}>{p.title}</a> <i>{p.journal}</i>, {p.year}</li>)}
import requests

papers = []
try:
    r = requests.get("https://www.bizgeniusapp.com/api/render.php", params={
        "key": "YOUR_WIDGET_KEY",
        "domain": "www.example.com",      # your website's domain
        "part": "list",
        "format": "json",
        "a[maxnum]": "10",
        "a[order]": "publicationDate",
    }, timeout=3)
    if r.status_code == 200:              # 204 = nothing to show
        papers = r.json()["papers"]
except requests.RequestException:
    pass                                  # build the page without citations

for p in papers:                          # escape when you print into HTML
    print(p["title"], p.get("journal"), p.get("year"))
<?php
$json = @file_get_contents('https://www.bizgeniusapp.com/api/render.php?' . http_build_query([
    'key'    => 'YOUR_WIDGET_KEY',
    'domain' => 'www.example.com',       // your website's domain
    'part'   => 'list',
    'format' => 'json',
    'a'      => ['maxnum' => '10', 'order' => 'publicationDate'],
]), false, stream_context_create(['http' => ['timeout' => 3]]));
$papers = json_decode($json ?: '{}', true)['papers'] ?? [];   // empty on 204 or failure
foreach ($papers as $p) { ?>
  <li><a href="<?= htmlspecialchars($p['url'] ?? '') ?>"><?= htmlspecialchars($p['title']) ?></a>
      <i><?= htmlspecialchars($p['journal'] ?? '') ?></i>, <?= (int)($p['year'] ?? 0) ?></li>
<?php }
curl -G "https://www.bizgeniusapp.com/api/render.php" \
  --data-urlencode "key=YOUR_WIDGET_KEY" \
  --data-urlencode "domain=www.example.com" \
  --data-urlencode "part=list" \
  --data-urlencode "format=json" \
  --data-urlencode "a[maxnum]=10"

A citation list (part=list) looks like this, shortened:

{
  "total": 2344,
  "start": 0,
  "papers": [
    {
      "id": 4436751,
      "title": "Regional brain volume abnormalities in Lesch-Nyhan disease and its variants: a cross-sectional study",
      "authors": ["David J Schretlen", "Mark Varvaris", "Tiffany E Ho", "..."],
      "journal": "The Lancet. Neurology",
      "year": 2013,
      "publication_date": "2013-12-01",
      "volume": "12", "issue": "12", "pages": "1151-8",
      "doi": "10.1016/s1474-4422(13)70238-2",
      "pubmed_id": "24383089",
      "url": "https://europepmc.org/articles/pmc3932536?pdf=render",
      "impact_factor": 54.6,
      "times_cited": 44,
      "snippet": "... created with the xjView (version 8.11 ...",
      "ai_summary": "In this study, the xjView product (version 8.11) was used to ...",
      "tags": ["VBM", "Lesch-Nyhan disease"]
    }
  ]
}

Each paper can have id, title, authors, journal, year, publication_date, volume, issue, pages, doi, pubmed_id, url, impact_factor, times_cited, products, snippet, abstract, ai_summary and tags. Fields with no value are left out. snippet, ai_summary, tags and times_cited are included unless you turn them off (a[show-snippet]=no ...); abstract only with a[show-abstract]=yes.

partWhat it returns
list
list-top
Your citations, or your top citations by impact factor: {"total", "start", "papers": [...]}. Up to 100 per call; page with start. total is null for list-top.
count-total-year-month
count-total, count-year, count-month
Your citation counts: {"total": 8296, "this_year": 814, "last_month": 172}
count-productOne product's count, with a[product-name]: {"product": "180", "dateRange": "all", "count": 2090}. With products[], up to 100 at once: {"results": [...]}.
aisummaryA short paragraph about your citations (see AI Summary): {"subject", "total", "this_year", "year", "countries", "institutions": [...], "companies": [...], "journals": [...], "text"}
top-journal
top-university
top-company
top-university-and-company
The names and logos in your logo strips: {"items": [{"name": "Blood", "logo": "https://..."}]}
list-figureFigures from citing papers, licensed for commercial display: {"total", "start", "figures": [...]}, 10 per call. Each has label, caption, image_url, pdf_url and paper.
list-new-papers-pubmed
list-new-papers-pmc
The latest PubMed or PubMed Central papers for a[keyword] (search results, not your citations): {"total", "start", "papers": [...]}, up to 20 per call.

Good practice (both formats)

  • Keep a copy for 12 to 24 hours. Your citations change daily at most, so there's no need to call us on every page view.
  • Wait at most 3 seconds. If we're slow or unreachable, build the page without citations (leave the box empty) and try again later.
  • A 204 response means "nothing to show", for example a product with no citations yet. Leave the box empty.
  • Send the same settings the box has. The same a[...] as its data-* attributes, and the same sourcekeyword as your citation.min.js URL, so the server copy and the widget show the same papers.
  • Print ready-made HTML as it is; escape data. The HTML is ready to use. In the JSON, all text is plain text: escape it when you print it into HTML.

Tip: give AI a paragraph to quote

The AI Summary box is the part AI assistants quote most easily: one paragraph with your citation counts, how many countries the research comes from, and well-known institutions, companies and journals among the citing papers. Every name in it appears in at least one approved citing paper. Add it to a product page or your publications page; the widget fills it in the browser, and all three setups above fill it on the server.

<!-- on a product page -->
<div class="bizgenius-citation-widget-container-aisummary" data-product-name="180" data-label="Pertussis Toxin"></div>

<!-- on your publications page: leave out data-product-name -->
<div class="bizgenius-citation-widget-container-aisummary" data-label="Your Company"></div>

"Researchers have cited Pertussis Toxin in 2,090 peer-reviewed publications, 93 of them from 2026. Citing authors work in at least 57 countries and regions, at institutions such as Harvard University, Johns Hopkins University, and Shanghai Jiao Tong University ..."

Check that it works

  1. Open a page with the widget and choose View Page Source (not Inspect: Inspect shows the page after JavaScript has run).
  2. Search the source for the title of a paper you see in the widget. If it's there, AI crawlers can read your citations. With setups 1 and 2, and with the feed's ready-made HTML, you'll find it inside <div class="bizgenius-ssr" ...>.

Or from a terminal, fetching the page the way ChatGPT's crawler does (a number above 0 means it works):

curl -s -A "GPTBot" https://www.example.com/publications/ | grep -c "bizgenius-ssr"

Nothing there? Check that the boxes are in the HTML and empty (see Before you start), clear your page cache, and reload. Still stuck? Contact us with the page address and we'll take a look.

Questions

Only that the citations appear about a second sooner. The widget looks and works the same: search, filters and paging still run in the browser.

Hardly. Each box is fetched once and then kept for 12 hours, and reading a kept copy takes a few milliseconds. When a copy runs out, that one page view waits for us, usually 0.1 to 1 second and never more than 3. A box that is the same on many pages, such as a counter in your footer, is fetched once for all of them.

Your page is sent exactly as before, and the widget loads in the browser as it always has. The plugin and the PHP file try again after 30 minutes.

No. Every visitor, person or crawler, gets the same HTML, and it shows the same citations as the widget. This is ordinary server rendering, which search engines recommend.

The citation list, top citations, the new PubMed / PMC paper lists, the figure slider (as a list of up to 10 figures), the citation counters, product citation counts, the search form, the journal / institution / company logo strips and the AI Summary. Maps stay browser-only. The citation list also carries schema.org structured data: each paper's title, authors, journal, date, DOI and the product it mentions.

A copy of each box, so we aren't called on every page view. The WordPress plugin keeps it in WordPress's own cache (transients). The PHP file writes small files to your system temp folder (by default /tmp/bizgenius-ssr), one per box, replaced every 12 hours; set BIZGENIUS_SSR_CACHE_DIR to use another folder. Nothing else is stored, and nothing about your visitors is sent to us.

For setups 1 and 2, and for the feed as ready-made HTML, yes: keep citation.min.js and the widget boxes as they are, because the widget still runs in the browser. For the feed as data (JSON), only if you also want the interactive widget on the page.

Yes. WP Rocket, LiteSpeed, Varnish, Fastly, Cloudflare and others save the filled page as usual. Clear the cache once after installing, so pages cached before then are refreshed.