Integrate

Backstage plugin

Render ScaleQuality's measured signals as an entity card in Backstage: AI durability, engineering maturity, code maturity and your tech radar, for the org, business unit or team the entity maps to. One click deep-links back into ScaleQuality for the full story.

What the card shows

One card, four signals, each read live for the scope the entity is annotated with. The card is theme-aware, so it stays legible in both Backstage themes.

SignalSourceProvenance
AI durability, survival rate and the rework it impliesgit-attributed telemetryMEASURED
Engineering maturity, L1 to L5assessment scorecardsMEASURED
Code maturity, a score out of 100 with sub-scoreszero-config DiagnosisMEASURED
Tech radar, counted by ringconfigured technology landscapeDECLARED
Note. A signal with no measurement yet renders as a dash, never as a fake value. An empty tile means nobody has measured that scope, and the card says so instead of guessing.

The card reads the public read-only Platform API through the Backstage backend proxy, so your org-scoped key is injected server-side and never reaches the browser.

Before you start

What you need

  • A Backstage app you can edit and redeploy.
  • Permission to create an API key in ScaleQuality, which means owner or admin.
  • A place to keep that key in your Backstage backend environment or secret store.
  • At least one scope measured in ScaleQuality, otherwise every tile renders empty.

This guide targets @scalequality/backstage-plugin 0.1.4, the version currently published on npm.

Install and configure

Five steps: add the package, create the key, configure the proxy, add the card, and tell Backstage which ScaleQuality scope each entity belongs to.

Install the plugin

Add the package to your Backstage frontend app.

# from your Backstage app root
yarn --cwd packages/app add @scalequality/backstage-plugin@^0.1.4

Create an API key in ScaleQuality

The key is scoped to your organization and read-only: it can read summaries for your org and nothing else. Name it after where it runs, so a rotation later is obvious.

  1. In ScaleQuality, open Developers.
  2. Press Create key and give it a name, for example Backstage production.
  3. Copy the key. It starts with sq_live_.
Important. The full key is shown once. If it is lost, rotate it on the same page: rotation issues a new secret and stops the old one immediately, so update Backstage before you rotate, not after.

Store it as SCALEQUALITY_API_KEY in the environment of your Backstage backend, or in whatever secret store that backend already reads. It must never be part of the frontend bundle.

Configure the backend proxy

Add the proxy endpoint to your app config. The plugin calls it by the id scalequality.

app-config.yaml
proxy:
  endpoints:
    '/scalequality':
      target: https://app.scalequality.io/v1
      changeOrigin: true
      headers:
        Authorization: 'Bearer ${SCALEQUALITY_API_KEY}'
Note. The proxy is what keeps the key server-side. The browser calls your own Backstage backend, and the backend adds the Authorization header. Pointing the plugin straight at the API would ship the key to every user of the portal.

Add the card to your entity page

Render the card wherever the entity overview lives. It sizes itself to the grid item you give it.

packages/app/src/components/catalog/EntityPage.tsx
import { EntityScaleQualityCard } from '@scalequality/backstage-plugin';

// inside the entity page grid, e.g. the overview content:
<Grid item md={6}>
  <EntityScaleQualityCard />
</Grid>

Map entities to a ScaleQuality scope

The card needs to know which scope an entity represents. Annotate the Component or Group with the matching ScaleQuality id.

catalog-info.yaml
metadata:
  annotations:
    scalequality.io/team-id: <team-uuid>
    # or: scalequality.io/bu-id: <bu-uuid>
    # or: scalequality.io/org-id: <org-uuid>

The most specific annotation wins: team over business unit over org. An entity with none of them renders the missing-annotation empty state rather than a number that belongs to somebody else.

The ids come from the catalog endpoint, or from the URL of the corresponding page in ScaleQuality.

curl https://app.scalequality.io/v1/catalog \
  -H "Authorization: Bearer sq_live_..."

API reference

Everything the plugin reads is public, read-only and gated by the API key. The same endpoints back any other internal developer portal, Port included.

EndpointReturns
GET /v1/catalogYour org, its business units and its teams, with the ids you use in the annotations.
GET /v1/entity/:scope/:idEvery signal for one entity in a single response. This is what the card reads.
GET /v1/summary/:scope/:idOne compact headline for the scope, with provenance and a deep link.
GET /v1/eng-maturity/:scope/:idEngineering maturity level, with its breakdown.
GET /v1/code-maturity/:scope/:idCode maturity score and sub-scores from the zero-config Diagnosis.
GET /v1/durability/:scope/:idAI durability: survival rate, human baseline and the rework it implies.
GET /v1/radar/:scope/:idTechnology landscape, counted by ring.
GET /v1/entitiesEvery org, business unit and team with all of its signals, in one array. Built for bulk sync.

scope is one of org, bu or team, and id is the uuid from the catalog. A key only reads inside its own organization: an id from another tenant answers 403, an id that does not exist answers 404, and any other scope value answers 400.

Response shape

Every endpoint answers with the same envelope: the subject, when it was measured, and one entry per signal carrying the value, its unit, a status, its provenance and a deep link back into ScaleQuality.

GET https://app.scalequality.io/v1/entity/team/<team-uuid>
{
  "subject":    { "type": "team", "id": "...", "name": "Payments" },
  "measuredAt": "2026-08-14T09:12:44.108Z",
  "signals": {
    "durability":   { "value": 89, "unit": "%",     "status": "ok",
                      "provenance": "MEASURED", "data": { ... }, "deepLinkUrl": "..." },
    "engMaturity":  { "value": 3,  "unit": "level", "status": "warn",
                      "provenance": "MEASURED", "data": { ... }, "deepLinkUrl": "..." },
    "codeMaturity": { "value": 74, "unit": "score", "status": "warn",
                      "provenance": "MEASURED", "data": { ... }, "deepLinkUrl": "..." },
    "techRadar":    { "value": 42, "unit": "count", "status": "ok",
                      "provenance": "DECLARED", "data": { ... }, "deepLinkUrl": "..." }
  }
}

status is one of ok, warn, risk or unknown. provenance is part of the contract, not decoration: MEASURED means the number came out of your environment, DECLARED means somebody configured it. A null value means it has not been measured yet, and the card renders a dash for it.

Troubleshooting

Almost every failed install lands on one of these.

What you seeWhat it meansFix
The card shows a missing-annotation empty stateThe entity carries none of the three ScaleQuality annotations.Add scalequality.io/team-id, scalequality.io/bu-id or scalequality.io/org-id to the entity and reload the catalog.
401 from the proxyThe key was revoked or rotated, or SCALEQUALITY_API_KEY is not set in the backend environment.Confirm the variable exists in the backend that serves the proxy, and that the key is still active under Developers.
403 for an id that existsThat id belongs to a different organization than the key does.Take the ids from the catalog endpoint called with this key, not from another tenant.
Every tile renders a dashThe scope resolves, but nothing has been measured for it yet.Run a diagnosis, complete an assessment, or connect an AI provider for that scope. The card fills in as measurements land.
The request never leaves BackstageThe proxy id does not match. The plugin calls the endpoint registered as scalequality.Keep the key in app-config exactly as it appears in step 3, slash scalequality.