Skip to content
Regulation to Control
REGULATION-TO-CONTROL GRAPH API

A rule changed. Which of your controls just became insufficient?

Send a change you have already read — citation, dates, and the obligations it creates — together with your own control inventory. Back comes the impact graph: which controls each obligation lands on and why, which obligations have no control at all, what evidence is now required, and the dated deadlines including the one nobody computes. Coverage is deny-by-default: an obligation nothing matches is a gap, never an assumption.

Free tier: 50 mapped changes/month Paid from $499/month No card to run the demo
Live mapping Ready

Runs the same engine the paid endpoint uses. Nothing is stored and no key is needed.

Press “Map this change” to call the live API.
The problem

The rule arrives as prose. The controls live in a spreadsheet. The link between them lives in someone's head.

Regulatory change monitoring tells you a rule moved. Nothing tells you which of your controls it moved, who owns them, or what an auditor will now ask you to produce.

The mapping is built once and then rots

Somebody spends a fortnight linking obligations to controls in a spreadsheet. Then a control is retired, a team is reorganised, a rule is amended — and nothing recomputes. The mapping is still there, still confident, and quietly wrong.

Coverage is asserted, not demonstrated

A cell says "covered". It does not say which control, why that control, when it was last tested, or whether the evidence behind it was collected before the rule even existed. In an examination, "covered" is a claim you now have to substantiate from memory.

The date on the calendar is the wrong date

Everyone tracks the effective date. The date that decides whether you make it is the effective date minus however long the work actually takes — 90 days for anything needing a committee. That subtraction is not in the rule and not in the spreadsheet.

What it costs

A gap is not something you discover. It is something you disclose.

Nothing alerts. The rule simply applies, and the first anyone hears of it is a request for evidence you cannot produce, about a control nobody built.

  • Gap
    An obligation that belonged to nobody

    It touched two domains, so it sat between two owners. Each assumed the other had it. There was no line in any spreadsheet saying "no control matches this", because a spreadsheet only records what someone thought to write down.

  • Evidence
    A control that works and cannot prove it

    The control operates. The policy document behind it was approved eighteen months before the amendment was published, so it evidences the old requirement, not the new one. Nobody compares collection dates to publication dates by hand.

  • Deadline
    Ninety days of work with sixty days left

    The obligation needs board sign-off. Board cycles are quarterly. That was knowable on the day the rule was published and became urgent on the day someone finally read it — by which point the only options left were expensive ones.

What it computes

Five things a mapping spreadsheet cannot tell you.

You supply the obligations and your control inventory. The engine does the set matching, the whole-day date arithmetic, and the part nobody enjoys: refusing to assume anything is covered.

OutputWhat it answersWhy it is hard by hand
CoverageWhich obligations are covered, which are weak, and which have no control at all.The answer changes whenever either side of the mapping changes.
Impact graphThe controls, owning teams and evidence one change touches — each link with its reason.The links live in three systems and one person's recollection.
EvidenceWhich required artefacts exist, which are undated, and which predate the rule.Nobody compares a collection date to a publication date.
DeadlinesEffective date, transition end, and the derived date work has to start.The prepare-by date is a subtraction nobody performs.
PriorityA score from four published weights, with the arithmetic attached.A ranking you cannot reproduce is one you cannot defend.

Rule change Obligations Controls Owning teams Evidence Dated deadlines

  • 01

    You state the obligations

    Citation, dates, and what the change requires — in your words. Nothing here reads the regulation or guesses at its meaning, which is exactly why the same input always produces the same output and why you can put a number in front of an examiner.

  • 02

    Matching is a set rule, not a score

    A control matches an obligation when the control's tags contain every tag the obligation requires. No threshold to tune, no similarity to explain. The answer to "why did this control come back?" is one sentence, and you can check it by hand.

  • 03

    Deny by default

    Nothing matched is a gap — never an assumption of coverage. Something matched that cannot currently satisfy the obligation is a weak. Those are different pieces of work: one is a control to build, the other is a control to fix.

  • 04

    Evidence has to be dated, and recent enough

    A required artefact that no matched control holds is missing. One with no collection date evidences nothing on any particular day. One collected before a new rule was published cannot demonstrate a requirement that did not yet exist. Three findings, three different fixes.

  • 05

    The prepare-by date, derived

    Compliance date minus the lead time the obligation type actually needs — 90 days for governance, 30 for a disclosure. Published per type, overridable per obligation, and computed in whole UTC days so it cannot drift an hour across a clock change.

  • 06

    A priority you can recompute

    Four weights — enforcement, coverage, urgency, evidence — summing to at most 100. Every response carries the derivation line by line, and the weights themselves are served from /v1/obligation-types. Nothing is asserted that cannot be checked.

Example output

One amendment, three obligations, three controls — and three different answers.

An amendment to a disclosure rule, mapped against a small inventory, as returned by POST /v1/changes. One obligation is covered, one is weak, one has no control at all — and a retention control in the inventory turns out to be untouched by the change.

{
  "changeId": "REG-2026-0117",
  "citation": "12 CFR 1026.19(e)",
  "evidenceCutoff": "2026-03-02T00:00:00.000Z",
  "summary": {
    "obligations": 3, "covered": 1, "weak": 1, "gaps": 1,
    "controlsMatched": 2, "controlsUnmatched": 1, "teams": 2,
    "highestPriority": "critical",
    "earliestDeadline": "2026-09-01T00:00:00.000Z"
  },
  "obligations": [
    {
      "obligationId": "OB-2", "coverage": "weak",
      "coverageReason": "1 control(s) match on (notification, payments), but none of them is currently operating and 2 required evidence kind(s) are not held.",
      "findings": [
        { "code": "control_not_implemented", "controlId": "CTL-027" },
        { "code": "control_untested",         "controlId": "CTL-027" },
        { "code": "evidence_missing" },
        { "code": "evidence_missing" }
      ],
      "priority": { "score": 73, "band": "critical", "derivation": [
        "enforcement = penalty -> 40",
        "coverage = weak -> 18",
        "urgency = 92 day(s) to 2026-12-01 (91-180 days) -> 5",
        "evidence = 2 of 2 required kind(s) unmet -> 10",
        "total = 40 + 18 + 5 + 10 = 73 -> critical" ] },
      "owners": ["Payments Engineering"],
      "exposureMinor": 250000000
    },
    {
      "obligationId": "OB-3", "coverage": "gap",
      "coverageReason": "Nothing in the inventory carries all of (disclosure, recordkeeping). Deny by default: an unmatched obligation is a gap, never an assumption of coverage.",
      "priority": { "score": 70, "band": "critical" }
    },
    { "obligationId": "OB-1", "coverage": "covered",
      "priority": { "score": 45, "band": "medium" } }
  ],
  "graph": { "edges": [
    { "from": "obligation:OB-1", "to": "control:CTL-014", "kind": "mapped_to",
      "reason": "Control CTL-014 carries every tag this obligation requires (disclosure, payments). Matching is a set rule over tags, not a similarity score." }
  ] },
  "unmatchedControls": ["CTL-101"],
  "exposure": { "currency": "USD", "totalMinor": 250000000,
    "byOwner": [{ "owner": "Payments Engineering", "amountMinor": 250000000 }],
    "unassignedMinor": 0 }
}

The gap is the output you are buying

OB-3 needs a control that is both a disclosure control and a records control. CTL-101 is only the second, so it does not match — and it is reported in unmatchedControls, not credited. A partial match is not coverage, and calling it one is how a gap survives an audit prep.

Every edge says why it exists

The mapping edge names the exact tags the control matched on. So when someone asks why CTL-014 is on the hook for OB-1, the answer is in the response, not in the memory of whoever built the spreadsheet.

You supply the obligations

This does not read regulations, parse PDFs, or infer requirements with a model. You send the obligations your team has already read out of the rule, and it does the set matching and dated arithmetic — deterministically, the same way every time.

This service is newly launched. We publish no uptime percentage, customer count, logo wall or certification claim until we have the operating history to support one. Output is arithmetic and graph mapping over the inputs you supply. It does not read, interpret or apply law, and it is not legal advice.

How it works

Three steps to a defensible mapping

  • Tag your control inventory once

    Scope tags, status, last test date, owning team and the evidence each control holds. You keep the inventory; this service stores none of it and you send it with each request.

  • POST the change

    Citation, dates and the obligations it creates. Back come coverage, findings, evidence status, deadlines and the impact graph. One unit per change, up to 50 per call against one inventory.

  • Route the gaps and the deadlines

    Every obligation carries a coverage state, an owning team and a prepare_by date. Push the gaps into whatever already gets worked — a queue, a GRC platform, a remediation board.

Rules the engine never bends

Deny by default, whole UTC days, integer money

  • An unmatched obligation is a gap. Never assumed covered, never omitted, never quietly rounded up to a partial match.
  • Matching is a stated set rule. The control's tags must contain every tag the obligation requires. It is checkable by hand, which is the whole point.
  • Dates are whole UTC days. A deadline that shifts an hour across a clock change is a missed deadline.
  • Evidence must postdate a new requirement. For a new_rule or an amendment, an artefact collected before publication cannot demonstrate it.
  • Every finding is returned, not the first. A remediation queue needs the whole reason; half a reason is a second round trip.
  • maxPenaltyMinor is integer minor units. A fractional amount is a 400, not a rounding, and an exposure split across teams sums exactly back to the total.
  • Output is deterministic. Same inputs, same evaluation instant, same bytes — so a mapping can be stored, diffed and defended two years later.
  • Nothing is stored. Every endpoint is stateless. Your control inventory is the most sensitive thing your GRC team holds, and the safest place for it is not here.
Developer integration

Small surface, versioned, OpenAPI-first

Authenticate with Authorization: Bearer <key>. Every error returns a stable code and a requestId you can quote, and validation failures name the exact field in details.path.

POST/v1/keysPublic. Emails a one-time claim token to the address you give it. No key is returned on this call — proving the mailbox is what stops anyone minting a key on your account.
POST/v1/keys/claimPublic. Spends the emailed token and returns the key, shown once — 50 mapped changes a month, no card.
POST/v1/changesMap one change or up to 50 against one control inventory. Billed one unit per change.
POST/v1/demo/mapPublic. Real engine, capped at 5 obligations and 20 controls. No key required.
GET/v1/obligation-typesPublic. Every obligation type, evidence kind, finding code and priority weight.
curl -X POST https://controlgraph-api.com/v1/changes \
  -H "Authorization: Bearer $REGULATION_CONTROL_KEY" \
  -H "content-type: application/json" \
  -d '{"controls":[{"controlId":"CTL-014","name":"Disclosure template review",
                 "owner":"Payments Compliance","status":"implemented",
                 "tags":["payments","disclosure"],"lastTestedAt":"2026-07-01"}],
       "change":{"changeId":"REG-2026-0117","citation":"12 CFR 1026.19(e)",
                 "title":"Revised cost-of-credit disclosures","jurisdiction":"US",
                 "changeType":"amendment","publishedAt":"2026-03-02",
                 "effectiveAt":"2026-09-01","transitionEndsAt":"2026-12-01",
                 "obligations":[{"obligationId":"OB-1","type":"disclosure",
                   "text":"Provide the revised disclosure within three business days.",
                   "appliesTo":["payments","disclosure"],"enforcement":"penalty"}]}}'
Pricing

Priced per mapped change, capped so a backlog import can’t surprise you

One unit is one regulatory change mapped against your inventory — however many controls you send and however many obligations it creates. Every plan includes an allowance, then a per-unit overage that is hard-capped per billing period: your worst-case bill is the base price plus the cap, never more.

Developer
$499 /month
  • 500 mapped changes included
  • $1.50 per additional mapped change
  • Overage capped at $1,000 per period
  • Full API access, Python and TypeScript SDKs
Start with this plan
Scale
$5,000 /month
  • 8,000 mapped changes included
  • $0.80 per additional mapped change
  • Overage capped at $10,000 per period
  • Full API access, Python and TypeScript SDKs
Start with this plan
Enterprise
$10,000+ /month
  • 25,000 mapped changes included
  • $0.50 per additional mapped change
  • Overage capped at $20,000 per period
  • Full API access, Python and TypeScript SDKs
Contact sales

Free tier: 50 mapped changes per month, no card. Prices in USD, billed monthly. Payment is processed by Square through our shared billing platform; card details never touch this site.

What counts as a mapped change?
One regulatory change in one call. Mapping 50 changes in a batch bills 50 units. The control inventory is not billed: sending 400 controls to map one change costs one unit, and re-sending them to map a second change in the same request costs one more. Charging per control would only teach you to send fewer of them, which is the opposite of what makes the answer correct.
Does it read regulations?
No, and it does not claim to. You supply the obligations your team has already read out of the change, along with your own control inventory. The API does the set matching, the coverage analysis and the dated arithmetic. There is no model interpreting law, which is why the same input always produces the same output — and why the result is arithmetic you can defend rather than a judgement you have to trust. It is not legal advice.
How does a control get matched to an obligation?
A control matches when its tags contain every tag in the obligation's appliesTo. Tags are trimmed, lower-cased and de-duplicated before comparison. That is the whole rule. It is a set test, not a similarity score, so there is no threshold to tune and the reason is printed on the graph edge: "carries every tag this obligation requires (disclosure, payments)".
What is the difference between a gap and a weakness?
A gap means nothing in your inventory matched — there is a control to build. A weak means something matched but cannot currently satisfy the obligation: it is planned rather than implemented, overdue for testing, or the required evidence is missing, undated, or older than the rule. That is a control to fix. Collapsing both into "not covered" would put two different pieces of work in one queue.
Where does the priority score come from?
Four published weights that sum to at most 100: enforcement (penalty 40, supervisory 25, guidance 10), coverage (gap 30, weak 18, covered 0), urgency by days to the compliance date, and 10 if any required evidence is unmet. Every response repeats the arithmetic line by line in priority.derivation, and GET /v1/obligation-types serves the weights themselves. A score nobody can recompute is a score nobody can defend.
Is my control inventory stored?
No. Every endpoint is stateless: you send the inventory with each request and get the mapping back. Nothing about your controls, your evidence or your gaps is retained here.

Find the gap before the examiner does.

The free tier is 50 mapped changes a month, no card. Create a key, post one change with your control inventory, and the response tells you what nothing covers.

curl -X POST https://controlgraph-api.com/v1/keys \
  -H "content-type: application/json" \
  -d '{"email":"you@company.com","name":"Evaluation key"}'

# 202 Accepted. A one-time token is emailed to that address — spend it,
# and the response carries the key, shown once.

curl -X POST https://controlgraph-api.com/v1/keys/claim \
  -H "content-type: application/json" \
  -d '{"token":"<token from the email>"}'