Recipes

Show equivalents on a product page

Render the equivalents of an item on your own product page, in the order given, with safety flags, the disclaimer and a link to who stocks it.

This is a worked example, not a supported connector. Interchange does not build or run connectors. You own the code you write from it, and the support boundary applies. Read the support boundary.

This recipe needs no key.

How it works

  1. Call from your server

    Fetch the equivalents on your server and cache the page for no more than 12 hours. A key, if you use one, must never reach a browser.

  2. Keep the order

    Equivalents arrive sorted by brand, A to Z. Do not reorder them by margin, stock or anything else.

  3. Keep near matches apart

    Near matches are different items. List them separately with the stated difference, never among the equivalents.

  4. Show the flags and the disclaimer

    Show every safety flag in the words given, and usage.disclaimer. Crediting Interchange with usage.attribution is a strong request, not a condition.

  5. Fail closed

    On a 410 show nothing for that item. On any other error show no equivalents rather than an old guess.

The code

Node 22, no libraries. Adapt it to your language and your storage.

JavaScript: render equivalents as HTML
const BASE = "https://api.interchangeparts.com.au";

const escapeHtml = (s) =>
  String(s).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);

// Run this on your server and cache the result for no more than 12 hours. Never call from a browser with a key.
export async function equivalentsHtml(ixId) {
  const response = await fetch(BASE + "/v1/items/" + encodeURIComponent(ixId) + "/equivalents?include_near=true", {
    headers: { Accept: "application/json" },
  });
  const answer = await response.json();

  if (response.status === 410) return ""; // withdrawn: show nothing at all
  if (response.status !== 200) return "<!-- equivalents unavailable -->"; // fail closed. Do not show a stale guess.

  const flags = answer.safety_flags
    .map((f) => '<p class="warning"><strong>' + escapeHtml(f.severity) + ":</strong> " + escapeHtml(f.message) + "</p>")
    .join("");

  // Show the order you were given. It is by brand, A to Z, and must not be re-ordered by anything else.
  const rows = answer.status === "no_verified_match"
    ? "<p>Interchange has no verified equivalent for this part. That does not mean none exists.</p>"
    : "<ul>" + answer.equivalents.map((e) =>
        "<li>" + escapeHtml(e.brand) + " " + escapeHtml(e.part_number) + " (reviewed " + escapeHtml(e.reviewed) + ")</li>").join("") + "</ul>";

  // Near matches fit the same way (or not) but are not the same part. Keep them apart, with the difference stated.
  const near = (answer.near_matches ?? []).map((n) =>
    "<li>" + escapeHtml(n.name) + ": " + n.differences.map((d) => escapeHtml(d.attribute + " is " + d.other_value + ", not " + d.this_value)).join(", ") + "</li>").join("");

  return [
    '<section aria-label="Equivalent parts">',
    "<h2>Equivalents of " + escapeHtml(answer.name) + "</h2>",
    flags, rows,
    near ? "<h3>Near matches, not the same part</h3><ul>" + near + "</ul>" : "",
    answer.meta.data_set === "sandbox" ? "<p>Sample data. Do not rely on it.</p>" : "",
    "<p>" + escapeHtml(answer.usage.disclaimer) + "</p>",
    '<p><a href="' + escapeHtml(answer.suppliers_url) + '">Who stocks it</a>. ' + escapeHtml(answer.usage.attribution) + "</p>",
    "</section>",
  ].join("");
}

Watch for

  • When meta.data_set is sandbox the data is illustrative. Do not publish it to your customers.
  • no_verified_match means we do not know. Say that, and do not offer the nearest part.

The calls are described in the API reference.