API & integration · v0.2

API & integration

There is deliberately no hosted TIR-CMM API. The assessment tool’s strongest property is that nothing you type ever leaves your machine, and that property is only credible if there is no server to send it to. Integrators get four layers instead — a JavaScript library, two JSON Schemas, a reference REST server you run yourself, and the machine-readable model — all of which keep assessment data inside the organisation that owns it.

The design decision, stated plainly. A hosted scoring endpoint would be trivial to build and would quietly undo the one guarantee this project makes. Every claim on this site about data never being transmitted rests on there being nowhere for it to go. So there is no api.tir-cmm.com, no account, no key, no rate limit and no telemetry — and there will not be one.

If your platform needs REST, run the reference server yourself. If it needs a function call, import the library. If it needs neither, read the schemas and the machine-readable model and build your own instrument against the same structure.

Four ways to integrate

Four artefacts, in decreasing order of coupling. Pick the lowest-coupling one that does the job: the model data outlives any particular implementation of it, and an integration built against the schemas survives changes to the library that an integration built against internals does not.

What each layer is, when to reach for it, and how it is licensed.
Layer What it is Use it when Licence
JavaScript library dist/tir-cmm.mjs and dist/tir-cmm.js — the scoring engine and the model data, no DOM code, no dependencies. You are scoring in JavaScript, in a browser, in Node, in a worker or in a build pipeline, and you want the constraint engine rather than a reimplementation of it. Source-available, all rights reserved. Free to run and self-host; not licensed for redistribution as your own product.
JSON Schemas The export contract and the TID-CMM import contract, published at tir-cmm.com/schemas/. You are consuming or producing TIR-CMM results in any language, or validating a payload at a boundary you do not control. CC BY 4.0 Build against them freely, including commercially.
Self-hosted REST server server/server.mjs — a dependency-free HTTP wrapper around the library. Your platform speaks REST and cannot import JavaScript, and you can run one Node process inside your own boundary. Source-available, all rights reserved. Free to run and self-host.
Machine-readable model data/tir-cmm-model.json and data/tir-cmm-model.yaml — the complete model as data. You are building your own instrument, generating documentation, mapping the model into a GRC schema, or citing it from an AI system. CC BY 4.0 Build against it freely, including commercially.

Note the split. The model is CC BY-ND 4.0 and the tooling is source-available, but the schemas and the machine-readable model are deliberately CC BY 4.0 so that integration is frictionless. You do not need permission to build against them, and you do not owe a fee for doing so commercially. The licence page sets out the whole position.

JavaScript library

The library is the scoring engine and the model data, and nothing else. It carries no DOM code, no rendering and no storage, which is what lets the same file run in a browser tab, in Node, in a service worker or in a build step that generates a report.

  • dist/tir-cmm.mjs — ES module. import { score } from './dist/tir-cmm.mjs'
  • dist/tir-cmm.js — classic script. Load it with a <script> tag and the API appears on window.TIRCMM. It also detects CommonJS and AMD, so require() works.

Both are about 100 KB, built from the same flattened source, with no dependencies at all. There is no build step to adopt and no package to install: copy the file, or serve it from your own origin.

A worked example

Score a partial assessment, then take the leadership view, the findings and the sequenced plan off the same result object. Every call below is on the published surface.

import {
  score, resolveDetection, readiness, weaknesses, blueprint,
} from './dist/tir-cmm.mjs';

// 1. Sub-capability scores. One entry per sub-capability you assessed:
//      v  = maturity 0-5, or null for not applicable
//      ev = evidence level 0-3, which caps v under constraint R3
//    A blank evidence level is treated conservatively as level 0.
const scores = {
  'RP-1': { v: 3, ev: 2 },   // IR plan and scope
  'RP-3': { v: 2, ev: 2 },   // roles, rotas and 24/7 reachability
  'RA-1': { v: 2, ev: 2 },   // pre-authorised containment
  'RA-2': { v: 1, ev: 1 },   // decision latency (MTTDecide)
  'RE-1': { v: 3, ev: 1 },   // playbook coverage - capped to 2 by R3
  'CE-1': { v: 3, ev: 2 },   // tiered containment options
  'RV-1': { v: 1, ev: 1 },   // tabletop exercise programme
  // ... the remaining sub-capabilities in scope
};

// 2. The Containment Lattice: attack-path stage x asset class, keyed 'S3-A1'.
//      rrs  = Response Readiness Status 0-3
//      tier = criticality 1 (T3) to 3 (T1, a crown jewel)
const lattice = {
  'S3-A1': { inScope: true, rrs: 0, tier: 3 },   // priv-esc on identity: blind, T1
  'S3-A2': { inScope: true, rrs: 2, tier: 2 },
  'S4-A1': { inScope: true, rrs: 1, tier: 3 },
  'S7-A7': { inScope: true, rrs: 3, tier: 2 },   // impact on data/backup: proven
};

// 3. Decide where the detection figure for constraint R4 comes from, and what
//    band ceiling that source justifies. An imported TID-CMM score carries no
//    ceiling; the prerequisite check caps at L4; nothing at all caps at L3.
const detection = resolveDetection({ imported: 2.34 });

// 4. Score it.
const r = score(scores, {
  detection,
  lattice,
  tempo: { actor: 'TA-01', breakout: 62, mttd: 180, mttdecide: 95, mttc: 40 },
});

console.log(r.band.id, r.band.name);          // constraint-adjusted band
console.log(r.overall, r.selfOverall);        // adjusted vs self-assessed
console.log(r.caps.map(c => c.id));           // which constraints capped the band
console.log(r.lattice.vrsCj);                 // crown-jewel-weighted VRS
console.log(r.tempo.margin, r.tempo.ratio);   // Containment Margin, Tempo Ratio

// 5. The three leadership lenses, re-cutting the same sub-capabilities.
//    The rehearsal ceiling applies here too: no lens may exceed RV + 1.
const lenses = readiness(r.effSubs, r.domains.RV);
for (const l of lenses) console.log(l.name, l.proven, l.verdict.label);

// 6. What is actually holding you back, and what to do about it.
const findings = weaknesses(r);
for (const w of findings) console.log(w.severity, w.title, w.fix);

const plan = blueprint(r, 6);   // up to 6 items per horizon bucket
for (const b of plan) console.log(b.name, b.window, b.total, b.items);

The example scores a handful of sub-capabilities for brevity. A real assessment supplies every sub-capability in scope — 58 at Baseline and Assurance, 20 at Pulse — and the full in-scope lattice. Sub-capabilities left out are treated as not assessed, not as zero.

The public surface

Forty-five exports: thirty data structures and fifteen functions. This is the whole contract. Anything not in the list below is an implementation detail and may change between versions without a major version bump — including internals you can reach from a bundled build. If something you need is missing from the published surface, say so rather than reaching past it.

Data exports

The model as data. All of it is also available, in language-neutral form, in the machine-readable model.

Thirty data exports. Structure and field names are stable within a schema version.
Export What it is
MODEL_VERSIONThe model version string, currently 0.2. Carry it into every output you produce.
BANDSThe six maturity bands L0 to L5, each with its score range, name and definition.
EVIDENCEThe four evidence levels 0 to 3, with what each expects and what it permits.
EVIDENCE_CAPThe R3 cap table: {0:1, 1:2, 2:4, 3:5}. A high evidence grade permits a high score; it does not create one.
STAGESThe eight attack-path stages S0 to S7 — the lattice rows — each with its leverage weight.
ASSETSThe eight asset classes A1 to A8 — the lattice columns.
RRSThe four Response Readiness Status values 0 to 3, from no option to proven under fire.
CRITICALITY_TIERSThe three cell criticality tiers T1 to T3 and their weights.
DOMAINSThe eight domains with their weights and their 58 sub-capabilities, each carrying its level-3 anchor, expected evidence and improvement action.
CONSTRAINTSThe seven integrity constraints R1 to R7, with what each caps and why.
CROSSWALKDomain-level mapping to NIST CSF 2.0, SP 800-61r3, ISO/IEC 27035, RE&CT, D3FEND, SOC-CMM and the regulatory regimes.
TIERSThe three assessment tiers — Pulse, Baseline, Assurance — with their time cost and band ceiling.
PULSE_SUBSThe twenty sub-capability IDs a Pulse assessment asks about.
TELEMETRY_ATTRSThe attributes each asset class’s response telemetry is graded on.
SCENARIOSThe twelve starter scenarios.
SCENARIO_STAGESThe ten lifecycle stages a scenario is scored across.
PASS_CONDITIONSWhat counts as passing a scenario rather than merely running it.
SCENARIO_RECORDThe minimum record a scenario run must carry to be admissible as evidence.
GOV_CHECKSThe governance checklist the tool actually scores.
CALIBRATIONCalibration questions asked of the assessor, not the assessed — each designed to find a score that is true on paper and false in practice.
DECISION_RIGHTSWho decides what during an assessment, and who challenges it.
GOVERNANCE_RULESThe rules an Assurance-tier assessment has to satisfy.
ANTI_GAMINGThe anti-gaming rules — the known ways a result gets flattered, named.
RACIResponsibility assignment across the assessment process.
BOARD_RULESHow to report a result to a board without it becoming a dashboard.
PUBLICATION_CAUTIONSWhat to be careful about when publishing a result externally.
LENSESThe three readiness lenses — respond, recover, resilience — and the weighted sub-capabilities behind each.
HORIZONSThe three roadmap horizons: quick fixes, ninety-day moves, structural work.
PREREQThe prerequisite check: the detection and threat-modelling questions that stand in for a TID-CMM import.
ACTIONSOne improvement action per sub-capability, each with effort, type, owner, the act and the outcome it buys.

Functions

Fifteen functions. Signatures are stable within a schema version.
Function Signature What it does
scorescore(scores, opts)The engine. Applies the integrity constraints and returns the whole result object. Documented in detail below.
bandForbandFor(n)Maps a 0–5 score to its band object.
resolveDetectionresolveDetection({ imported, prereq })Decides which detection figure constraint R4 uses and what band ceiling that source justifies: imported TID-CMM score, no ceiling; prerequisite check, L4; nothing, L3.
readinessreadiness(effSubs, rvDomainScore)Scores the three leadership lenses from the R3-adjusted sub-capability scores, applying the rehearsal ceiling of RV + 1. Returns claimed, proven and a verdict per lens.
weaknessesweaknesses(r)Turns a result into ranked findings: kind, id, severity, title, body and fix.
blueprintblueprint(r, limitPerBucket)Sequences the roadmap into the three horizon buckets, each item carrying an owner, a type, the action and the outcome. Defaults to six items per bucket.
scenarioScorescenarioScore(state)Scores scenario exercising: how many were in scope, run, passed, and run but never timed.
governanceScoregovernanceScore(state)Scores the governance checklist and returns any band ceiling that assessment quality itself justifies.
telemetryScoretelemetryScore(telemetry, assets)Grades response telemetry per asset class against the telemetry attributes.
stateReportstateReport(r, { org })The current-state narrative in plain language, with no model jargon in it.
headlineheadline(r)One sentence naming the single most important thing the result says.
tierByIdtierById(id)Looks up an assessment tier by 'pulse', 'baseline' or 'assurance'. Falls back to Baseline.
prereqScoresprereqScores(answers)Averages the prerequisite check into a detection figure, a threat-modelling figure and a count of how many questions were answered.
pulseCoveragepulseCoverage()Reports which domains the twenty Pulse questions reach and how thinly.
evidenceLevelOfevidenceLevelOf(rec)Reads the evidence level off a score record, treating a blank level conservatively as 0.

score(scores, opts) in detail

The first argument is an object keyed by sub-capability ID, each value { v, ev } where v is the 0–5 maturity score or null for not applicable, and ev is the evidence level 0–3. The second argument is options.

Every option is optional. Omitting one is a meaningful choice, not a neutral one.
Option Type Effect
detection object The resolved detection object from resolveDetection(): { value, source, ceiling, note }. Its value supplies D for constraint R4, which caps RE, CE and FI at D + 1, and its ceiling caps the band when detection maturity is self-assessed or absent. A bare detectionScore number is still accepted for backwards compatibility.
lattice object The Containment Lattice, keyed 'S3-A1', each cell { inScope, rrs, tier }. Drives the Validated Response Score, the crown-jewel weighted VRS, the engineered and proven rates, the blind-cell list and constraint R6. Omit it and the lattice metrics are all zero.
tempo object | null { actor, breakout, mttd, mttdecide, mttc } in minutes. Produces the Containment Margin and the Tempo Ratio and satisfies constraint R5. Omitting it caps the band at L3, because without tempo evidence L4 and L5 are unassessable rather than merely unproven.
ceilings array Extra band ceilings contributed by constraint R7 — assessment depth and governance quality — each { id, bandId, reason }. This is how the tier ceiling and the Assurance-tier governance ceiling are injected.
skipEvidenceCap boolean Disables the R3 evidence cap. Set this only for a Pulse assessment, which does not ask for evidence at all and so must not be penalised as though evidence were absent — its L3 band ceiling carries that weight instead. Setting it anywhere else produces a number the model does not stand behind.

score() returns a single object:

  • overall and selfOverall — the constraint-adjusted score and the score before any constraint was applied. The gap between them is the part of the capability that is currently assumed rather than demonstrated.
  • domains, rawDomains and preConstraint — adjusted, self-assessed, and the post-R3 baseline used for reporting each constraint’s independent effect.
  • band and caps — the final band object, and every ceiling that was applied to it with its constraint ID and reason.
  • adjustments and r3Hits — per-constraint detail, separating marginal from independent effects, plus every sub-capability the evidence cap actually lowered.
  • latticevrs, vrsCj, engineered, proven, cell count, and the blind Tier-1 and Tier-2 cells.
  • tempo — the resolved tempo object with margin and ratio, or null if tempo evidence was not supplied.
  • detection, detectionScore — what R4 used and where it came from.
  • roadmap — capability and lattice gaps merged and ranked by normalised impact.
  • effSubs — the R3-adjusted sub-capability scores, which is what you pass to readiness().

JSON Schemas

Two contracts, both JSON Schema draft 2020-12, both licensed CC BY 4.0. That is deliberate and it is the whole point: integration should be frictionless, so you may build against them freely, including in commercial products, with attribution and without asking.

Both schemas are canonical at these URLs and ship in the downloadable bundle.
Schema URL What it is for
Assessment export https://tir-cmm.com/schemas/tir-cmm-export.schema.json The result of a TIR-CMM assessment: band, adjusted and self-assessed scores, per-domain scores, lattice metrics, blind cells, tempo, constraint adjustments, readiness lenses and scenario counts. Emitted by the assessment tool and consumable by UTIOM roadmap tooling, GRC platforms and reporting pipelines. Requires schema, model_version, overall, band and domains.
TID-CMM import https://tir-cmm.com/schemas/tid-cmm-import.schema.json What TIR-CMM consumes from a TID-CMM detection assessment: detection maturity, crown jewels, modelled attack paths, priority actors with breakout times, and in-scope techniques with their ATT&CK mitigation codes. Every field is optional except the schema tag, because TIR-CMM runs standalone and a partial import simply pre-fills less.

The field that carries the most weight

detection_score_pre_substitution supplies D for constraint R4, which caps the response-execution, containment and forensics domains at D + 1: you cannot respond to what you never saw.

It is deliberately the pre-substitution figure. Once a TIR-CMM assessment exists, TID-CMM substitutes the TIR-CMM overall into its own incident-response domain. If TIR-CMM then read the post-substitution TID-CMM score, the response score would be feeding the constraint that governs it, and both numbers would inflate each other quietly and indefinitely. Reading the pre-substitution figure breaks that loop. The ordering is stated in both specifications, and an integration that supplies the wrong figure will produce a result that looks better than it is.

Self-hosted REST server

A minimal HTTP wrapper around the library, provided so that platforms which need REST can have it without assessment data leaving the organisation that owns it. You run it, inside your own boundary, so the data never reaches anyone else. That is the only form of TIR-CMM REST there is, and the only form there is going to be.

  • Zero dependencies. Node’s own http, fs, url and path modules, nothing else. No install step.
  • Node 18 or later, for top-level await and native ES module import.
  • No storage. Nothing is written to disk and nothing is logged beyond the startup line. Every response is sent with cache-control: no-store and an x-tir-cmm-version header.
  • Request bodies are capped at 2 MB.
node server/server.mjs --port 8787

TIR-CMM reference server v0.2 on http://localhost:8787
Assessment data stays in this process. Nothing is sent anywhere.

The port may also be set with the PORT environment variable; it defaults to 8787.

Endpoints

Eight endpoints. Every response is JSON; errors return 400 with an error string, unknown routes 404 with the route list.
Endpoint Returns
GET /healthLiveness: { ok, model, version }.
GET /modelThe complete machine-readable model, served straight from data/tir-cmm-model.json.
GET /model/domainsThe eight domains with their weights and all 58 sub-capabilities.
GET /model/constraintsThe seven integrity constraints.
GET /model/tiersThe three assessment tiers with their band ceilings.
GET /model/scenariosThe twelve scenarios, the ten scenario stages and the pass conditions.
POST /scoreScores an assessment and returns the full export payload, shaped to the tir-cmm/export/0.2 schema.
POST /reportEverything /score returns, plus the headline, the plain-language state report, the ranked findings and the full sequenced blueprint.

Scoring a request

Both POST endpoints take the same body. Every key is optional: scores, lattice, tempo, telemetry, scenarios, governance, prereq, detectionScore, tier, and organisation for /report. The server resolves detection, looks up the tier, scores governance, assembles the R7 ceilings and calls the library exactly as the assessment tool does.

curl -s http://localhost:8787/score \
  -H 'content-type: application/json' \
  -d '{
    "tier": "baseline",
    "organisation": "Meridian Group",
    "detectionScore": 2.34,
    "scores": {
      "RP-1": { "v": 3, "ev": 2 },
      "RA-1": { "v": 2, "ev": 2 },
      "RA-2": { "v": 1, "ev": 1 },
      "RE-1": { "v": 3, "ev": 1 },
      "CE-1": { "v": 3, "ev": 2 },
      "RV-1": { "v": 1, "ev": 1 }
    },
    "lattice": {
      "S3-A1": { "inScope": true, "rrs": 0, "tier": 3 },
      "S4-A1": { "inScope": true, "rrs": 1, "tier": 3 },
      "S7-A7": { "inScope": true, "rrs": 3, "tier": 2 }
    },
    "tempo": { "actor": "TA-01", "breakout": 62,
               "mttd": 180, "mttdecide": 95, "mttc": 40 }
  }'

The response is the export payload. Abbreviated, and using the specification’s worked example rather than the shortened body above:

{
  "schema": "tir-cmm/export/0.2",
  "model_version": "0.2",
  "tier": "baseline",
  "overall": 2.32,
  "self_assessed": 2.69,
  "band": "L1",
  "band_name": "Documented",
  "band_capped_by": ["R5", "R6"],
  "domains": { "RP": 2.80, "RA": 1.90, "RE": 2.40, "CE": 2.50,
               "AO": 2.20, "FI": 2.00, "RV": 1.60, "RG": 2.30 },
  "detection_score": 2.34,
  "detection_source": "tid-cmm",
  "readiness": {
    "respond": { "proven": 2.10, "claimed": 2.44, "verdict": "Partial" }
  },
  "vrs_cj": 0.41,
  "engineered_rate": 0.406,
  "proven_rate": 0.031,
  "blind_cells_t1t2": [ { "stage": "S3", "asset_class": "A1", "tier": "T1" } ],
  "tempo": [ { "actor": "TA-01", "breakout_min": 62,
               "mttd_min": 180, "mttdecide_min": 95, "mttc_min": 40,
               "containment_margin_min": -253, "tempo_ratio": 5.08 } ],
  "constraint_adjustments": [ { "constraint": "R1", "domains": ["RP","CE","AO"] } ]
}

Say it again, because it matters. This server exists so that nobody has to send an assessment to us. Run it on localhost, in your own container, on an air-gapped network — anywhere inside your own boundary. If you find yourself pointing a client at a TIR-CMM server you do not operate, something has gone wrong, because there is no official one to point at.

Machine-readable model

The whole model as data, in two formats with identical content: data/tir-cmm-model.json for programs, and data/tir-cmm-model.yaml for humans, diffs and review. Licensed CC BY 4.0.

It carries:

  • the eight domains with weights, the question each one answers, and their 58 sub-capabilities — each with its weight, its level-3 anchor, the evidence it expects, and its improvement action with effort, type, owner, the act and the outcome;
  • the eight attack-path stages and eight asset classes that form the Containment Lattice, plus the Response Readiness Status scale and the three criticality tiers;
  • the seven integrity constraints, the six maturity bands, the four evidence levels and the evidence cap table;
  • the three assessment tiers with their ceilings, the Pulse subset, the telemetry attributes, the prerequisite check, the three readiness lenses and the three horizons;
  • the twelve scenarios with their lifecycle stages and pass conditions, the governance checks, the calibration questions and the anti-gaming rules;
  • the crosswalk to NIST CSF 2.0, SP 800-61r3, ISO/IEC 27035, RE&CT, D3FEND, SOC-CMM and the regulatory regimes;
  • and the licence block, so the terms travel with the data.

This is the best artefact to cite. If you are building documentation, a GRC mapping, a training course or an AI system that needs to answer questions about TIR-CMM accurately, read this file rather than scraping the pages: it is generated from the same source the tool runs on, so it cannot drift from the model, and it carries the version and licence in the payload. The site also publishes llms.txt and llms-full.txt for the same reason.

Versioning and stability

  • The export schema tag is tir-cmm/export/0.2. It is a const in the schema, so a payload carrying any other tag is invalid against it rather than merely unfamiliar.
  • The model version travels in every payload, in model_version, in the machine-readable model, in the library as MODEL_VERSION and in the server’s x-tir-cmm-version response header. Record it alongside any result you store; a score without its model version cannot be compared with anything later.
  • Breaking changes bump the schema tag. A change to the meaning of a field, the removal of a field, or a change to how a constraint is applied all count as breaking. Additive fields do not: both schemas set additionalProperties: true, so a consumer that ignores unknown keys will keep working.
  • The published API list is the contract. Anything outside it — internal helpers reachable in a bundled build, the exact shape of intermediate objects, the wording of a generated narrative — may change without a major bump.
  • TIR-CMM is at v0.2, a draft for review. Several design questions are deliberately open. Expect the model to change before v1.0, and pin the version you built against.

Attribution

If your product integrates TIR-CMM, credit it, name the version, and link to the canonical source. A reasonable citation is:

TIR-CMM v0.2 - Threat-Informed Response Capability Maturity Model,
Reza Adineh, https://tir-cmm.com

In a user interface, a line such as “Maturity scoring based on TIR-CMM v0.2 (CC BY-ND 4.0) — tir-cmm.com” near the result satisfies it. In a machine-readable output, carry model_version and the schema tag, which you get for free by emitting a conforming payload.

Two things attribution does not license, and both are on the licence page in full: you may not publish a modified, rebranded or “extended” version of the model, and you may not redistribute the tooling as your own product. You may build whatever you like against the schemas and the machine-readable model, run the reference server, self-host the tool inside your organisation, assess clients commercially, and publish the results — those are yours.

Building something and unsure whether it falls inside the licence? Ask. The about page explains how, and permission for translations, workbook variants and similar is usually given. The only changes withheld are the ones that would weaken the integrity constraints.