SaaS Ranking

Data and API

Everything on this site is available as JSON, read only, without a key. 6 endpoints, CC BY 4.0, one rule that shapes all of it.

No field is ever a boolean. "iso_27001": true would be the sentence “this vendor is certified” – a statement of fact about a real company. What you get instead is found, not_found or not_measured, each with the address we read it at and the date. not_found means we did not find it at the addresses listed in checked; it is not a statement that the vendor has none.

Endpoints

EndpointAnswers
/api/v1/criteriaWhat do you measure, and how is it weighted?
The full catalogue with the weight of each criterion. This is the same source the methodology page reads.
/api/v1/categoriesWhich categories exist?
Every category we have measured at least once, with the number of vendors in it.
/api/v1/categories/crmWho is in a category, and in what order?
The ranking of one category: rank, score, and how many criteria we could measure for each vendor.
/api/v1/vendors/stripe.comWhat did you find at one vendor?
One vendor by domain, with every criterion, its source, the addresses we checked, and the quotes we read.
/api/v1/detailsWhat values did you read off their pages?
The catalogue of measured details: certifications named, hosting locations, uptime figures, subprocessors, third-party hosts.
/api/v1/details/certificationsWhich vendors name a given value?
One detail type with every value that has enough vendors to have a page.

All of them are GET. Anything else answers 405: corrections go through the form so a human reads them. Access-Control-Allow-Origin: * is set, so you can call them from a browser.

Fields every response carries

FieldWhat it means
snapshotThe id of the data snapshot this response was rendered from, for example r42.71638. Two figures are only comparable when they carry the same id.
snapshot_runThe highest completed measurement run that contributed to the snapshot.
snapshot_resultsHow many results the snapshot holds. It moves when a partial run lands, even if the run number does not.
snapshot_measured_throughThe newest measurement timestamp in the snapshot.
licenseThe licence the figures are published under: CC BY 4.0. Attribution is the link back.
score_versionThe version of the transparency score. It is frozen, and a new criterion does not change an existing figure.
classifier_versionThe current confidence classifier for stored certification and hosting quotes. It does not date or replace their original measurement method. Measured precision and recall are on /data/quality.
classifier_scopecurrent_quote_confidence: only the confidence of current stored quotes is re-evaluated. Original subjects, measurement dates, criteria, scores and historical series keep their recorded state.

The three states, and the fourth thing that is not a state

ValueWhat it meansWho it describes
foundWe found it, and source is where.the vendor
not_foundWe reached at least one address and none of them carried it. checked lists which.the vendor
not_measuredWe reached no address at all, or robots.txt asked us not to. It falls out of the numerator and the denominator of the score.us

A quoted detail carries a fourth thing, and it is not a state but a subject: whose sentence it is. anbieter (the vendor itself), infrastruktur (their data centres or infrastructure), dienstleister (a named provider of theirs), erwaehnt (named without a certification claim), unklar (unclear from the sentence). Only anbieter is aggregated into a value page – a sentence about a vendor's data centre does not belong in a list called “vendors that name ISO 27001 for themselves”.

The score

The score is Transparency Score v1: the share of the summed weights of 12 criteria that we found. It is frozen. Adding a criterion would change every figure ever published, including the one in a procurement file from last week, without the vendor doing anything; new criteria are therefore measured and returned as facts and do not enter the figure. Below 9 measured criteria there is no score at all – score is null, never 0.

BandScore
A85 to 100
B70 to 84
C55 to 69
D40 to 54
E25 to 39
F0 to 24

The 12 criteria

KeyNameWeightScored
dpaData processing agreement3yes (v1)
subprozessorenSubprocessor list3yes (v1)
datenregionData location stated3yes (v1)
statusseiteStatus page with history2yes (v1)
preisePublic pricing2yes (v1)
zertifizierungenCertifications named1yes (v1)
slaUptime SLA with a figure1yes (v1)
transportHTTPS enforced with HSTS2yes (v1)
tracker_vor_einwilligungNo third-party tracking before consent2yes (v1)
security_txtsecurity.txt (RFC 9116)1yes (v1)
subprozessor_meldungNotice before subprocessors change2yes (v1)
datenschutzkontaktNamed privacy contact1yes (v1)

The 10 detail types

KeyNameFrom criterionHas value pages
zertifikatCertification namedzertifizierungenyes
datenregionHosting location nameddatenregionyes
sla_prozentUptime figureslayes
subprozessorSubprocessor namedsubprozessorenyes
meldefrist_tageSubprocessor notice periodsubprozessor_meldungyes
datenschutzkontakt_artPrivacy contactdatenschutzkontaktyes
drittanbieterThird-party host on first loadtracker_vor_einwilligungyes
hsts_fristHSTS max-agetransportno
hsts_optionHSTS optiontransportno
security_txt_ablaufsecurity.txt expirysecurity_txtno

The machine-readable description

/openapi.json is an OpenAPI 3.1 document for this interface. It is generated from the same catalogues as the answers, not written alongside them: the endpoints, the shared fields, the twelve criteria, the detail types, the subjects and the three states all come from the code that produces the responses. A description written by hand goes stale exactly when someone adds a field, and a description that says something other than the interface is worse than none.

Responses carry an ETag, so a conditional request with If-None-Match gets a 304 and no body. There is no key and no quota beyond ordinary fair use; if you plan a large run, read how our own crawler behaves and do the same.

A response, in full

{
  "snapshot": "r178.108284",
  "snapshot_run": 178,
  "score_version": "v1",
  "license": "CC BY 4.0",
  "domain": "example.com",
  "score": 68,
  "measured": 12,
  "of": 12,
  "criteria": {
    "dpa": {
      "result": "found",
      "source": "https://example.com/legal/dpa",
      "checked": [
        {
          "url": "https://example.com/dpa",
          "outcome": "404"
        },
        {
          "url": "https://example.com/legal/dpa",
          "outcome": "200"
        }
      ],
      "note": null,
      "measured_on": "2026-09-04T02:41:18.000Z"
    },
    "security_txt": {
      "result": "not_found",
      "source": null,
      "checked": [
        {
          "url": "https://example.com/.well-known/security.txt",
          "outcome": "404"
        }
      ],
      "note": null,
      "measured_on": "2026-09-04T02:41:24.000Z"
    }
  },
  "details": [
    {
      "key": "zertifikat",
      "name": "Certification named",
      "value": "iso-27001",
      "subject": "anbieter",
      "quote": "We are ISO/IEC 27001 certified, audited annually.",
      "source": "https://example.com/trust",
      "measured_on": "2026-09-04T02:41:20.000Z",
      "path": "/data/certifications/iso-27001"
    }
  ]
}

Bulk: the whole dataset as one file

Every result we hold, gzipped, at /data/dump/. It is written once per measurement run and handed out by the web server, so it costs you one request and us nothing. manifest.json names the snapshot, the row count and a SHA-256 per file: two downloads with the same snapshot identifier are the same data.

Generating this per request would serialise around 90 MB on a two-core machine that also serves everything else here, which is why it is a file and not an endpoint.

Bulk: the evidence pack for one vendor

Every vendor profile carries a downloadable pack for a procurement file, in two formats:

Both carry the snapshot id and the disclaimer. There is no PDF: the container that serves this page has no browser to render one, and the page itself prints.

For an agent: MCP

The same data over Model Context Protocol at https://saas-ranking.com/mcp, so a model can ask instead of fetching. Stateless: one JSON-RPC request per POST, no session, no key, no account. 6 tools, all read-only:

ToolWhat it answers
criteriaThe twelve public signals we check, with their weight in the score and why each one matters. Start here if you do not know what this dataset contains.
searchSearch vendors and categories by name or domain, tolerant of typos. Returns names and paths, not measurements: use "vendor" for what we measured.
vendorAll twelve criteria for one vendor, each with the addresses we checked, the date, and the source where we found it. Takes a domain (example.com). Also returns the quoted details we read off their pages.
categoriesEvery category we have measured, with how many vendors it holds. Paginated.
categoryThe vendors of one category with their transparency score and rank. A score of null means too few criteria could be measured to rank the vendor, never a bad score.
detail_valuesFor one kind of detail (for example hosting locations or named certifications): which values we read on vendor pages, and at how many vendors. Every value links to a page that lists them with the sentence we read it from.

Every tool runs the same function as the REST endpoint beside it, and test/mcp.test.js compares both answers field by field – two paths would be two truths. Every answer carries the snapshot id and one sentence: a result describes our measurement, never a property of the vendor.

Rate limits, and what we ask instead

There is no key and no counted quota. What we ask is the thing a rate limit would enforce badly: take the snapshot once and work from it, rather than fetching ten thousand vendor documents one at a time. That is exactly what the bulk download is for.

If your use needs something we do not offer – a webhook, an older snapshot – tell us. We would rather build it than watch someone scrape the pages.

Versioning

The path carries the version: /api/v1/. Inside a version we add fields and never repurpose one. A field that would have to mean something else gets a new name, and the old one keeps its meaning until the next version. The score has its own version, printed in every response, for the same reason.