Loading…
● Scoring Engine · Integration Guide

One engine.
Every repo and site.

The Tri-Lens scoring engine is the single source of truth for every trust score at SiiYa. This page is the contract: how any repo, site, or service connects to it — securely, consistently, and the same way every time.

Status. The integration pattern below (auth, CORS, the consumer snippet, the fallback contract) is authoritative. The exact endpoint field names are the current target and are being confirmed against the tri-lens-pipeline engine — treat response shapes as the working contract, not a frozen spec, until this banner is removed.

1 · The model

Nothing computes its own trust scores. The engine (trilens-api, deployed on Cloud Run) owns scoring; every surface is a consumer that reads from it over HTTPS. This is what makes scoring seamless across repos: add a new site tomorrow and it speaks to the same engine, the same way.

PieceWhat it isWhere it lives
Scoring engineScores articles, serves stats & feedtri-lens-pipeline → Cloud Run
Consumer (site)Reads scores, renders themsiiya-website, agent-command-center, future surfaces
This contractHow consumers connectMirrored here + in tri-lens-pipeline

Engine base URL: https://trilens-api-782954600608.us-central1.run.app — referenced once per consumer (see §5), never hard-coded in many places.

2 · Authentication — one API key per consumer

Every consumer (each site / service / repo) gets its own API key. Keys are sent on every request in the X-Api-Key header. One key per consumer means access can be rate-limited, rotated, or revoked for a single surface without touching any other.

GET /feed HTTP/1.1
Host: trilens-api-782954600608.us-central1.run.app
X-Api-Key: tl_live_<your-consumer-key>
Accept: application/json
ActionOwnerHow
Issue a keyPlatform team (tri-lens-pipeline)One key per consumer, prefix tl_live_ (or tl_test_)
Store a keyConsumerAs a secret — env var / CI secret / hosting secret. Never in committed source.
Rotate / revokePlatform teamPer-consumer, no impact on other surfaces

3 · Security — secret keys never ship to the browser

A static site's JavaScript is fully public — anything in it can be read with “View Source.” A secret API key pasted into front-end JS is therefore not secret. Pick the consumer type that matches the surface:
Consumer typeKeyPattern
Server-side
API, function, backend
Secret key in env Call the engine from the server; never expose the key to clients.
Browser / static site
e.g. apps.siiya.online
Domain-restricted publishable key Key only works from allowlisted origins (Origin/Referer + CORS, §4). Or proxy through a tiny serverless function that holds the secret key.

Recommendation for read-only public data (stats, feed) on browser surfaces: a domain-restricted publishable key + CORS allowlist gives the simplest customer experience while keeping the secret server keys server-side.

4 · CORS — origin allowlist

Browsers only let a page read the engine if the engine returns the page's origin in its CORS headers. So every new browser consumer must have its origin allowlisted on the engine. Without this, a correct key still yields a blocked request.

Access-Control-Allow-Origin: https://apps.siiya.online
Access-Control-Allow-Headers: X-Api-Key, Content-Type
Access-Control-Allow-Methods: GET, OPTIONS

Current/target allowlist: apps.siiya.online, admin.siiya.online, plus any partner origins added per consumer.

5 · Endpoints

GET/stats

Top-line scoring totals (the numbers in the site’s stat bar).

{
  "articles_scored": 31624,
  "gold_articles": 412,
  "avg_trust_score": 62.7,
  "trust_dimensions": 3
}
GET/feed

Recently scored articles (the “Today’s scored coverage” list). Array, or an object with items / articles / feed.

[
  {
    "title":  "Central banks signal coordinated rate policy",
    "source": "Reuters",
    "url":    "https://…",
    "trust_score": 88
  }
]

Score → badge tier

Every score maps to one of four tiers (consumers render the badge from the score):

TierRange
Gold85 – 100
Well-Sourced60 – 84
Under Review30 – 59
High Risk0 – 29

6 · The integration pattern (drop-in)

Every consumer follows the same shape: render a fallback first, then replace it with live data. A surface must never break if the engine is briefly unreachable — it degrades to last-known/sample data. This is exactly how assets/scoring.js in this repo works; copy it as the reference.

const API = "https://trilens-api-782954600608.us-central1.run.app";
const KEY = window.TRILENS_KEY; // injected at deploy, never committed

async function loadFeed() {
  renderFeed(SAMPLE, { sample: true });           // 1. never blank
  try {
    const res = await fetch(`${API}/feed`, {
      headers: { "X-Api-Key": KEY, Accept: "application/json" },
    });
    if (!res.ok) return;                           // 2. keep fallback
    const data = await res.json();
    const items = Array.isArray(data) ? data : (data.items || data.articles || data.feed || []);
    if (items.length) renderFeed(items, { sample: false });  // 3. go live
  } catch (_) { /* engine unreachable — keep fallback */ }
}

7 · Adding a new repo or site

Four steps, every time:

  1. Request a key from the platform team — one tl_live_ key for the new consumer.
  2. Get the origin allowlisted on the engine (CORS, §4) if it runs in a browser.
  3. Store the key as a secret (env / CI / hosting secret), injected at deploy — never committed (§3).
  4. Copy the consumer snippet (§6) — fallback first, live on success.

That’s the whole point of one engine + one contract: a new surface is a key, an allowlist entry, and ~20 lines — not a re-implementation of scoring.