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.
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 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.
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.
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.
| Output | What it answers | Why it is hard by hand |
|---|---|---|
| Coverage | Which obligations are covered, which are weak, and which have no control at all. | The answer changes whenever either side of the mapping changes. |
| Impact graph | The controls, owning teams and evidence one change touches — each link with its reason. | The links live in three systems and one person's recollection. |
| Evidence | Which required artefacts exist, which are undated, and which predate the rule. | Nobody compares a collection date to a publication date. |
| Deadlines | Effective date, transition end, and the derived date work has to start. | The prepare-by date is a subtraction nobody performs. |
| Priority | A 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 aweak. 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.
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.
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_bydate. Push the gaps into whatever already gets worked — a queue, a GRC platform, a remediation board.
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_ruleor anamendment, 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.
maxPenaltyMinoris 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.
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.
/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./v1/keys/claimPublic. Spends the emailed token and returns the key, shown once — 50 mapped changes a month, no card./v1/changesMap one change or up to 50 against one control inventory. Billed one unit per change./v1/demo/mapPublic. Real engine, capped at 5 obligations and 20 controls. No key required./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"}]}}'
Python and TypeScript SDKs Service health: https://controlgraph-api.com/health
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.
- 500 mapped changes included
- $1.50 per additional mapped change
- Overage capped at $1,000 per period
- Full API access, Python and TypeScript SDKs
- 2,500 mapped changes included
- $1.10 per additional mapped change
- Overage capped at $4,000 per period
- Full API access, Python and TypeScript SDKs
- 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
- 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
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?
Does it read regulations?
How does a control get matched to an obligation?
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?
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?
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?
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>"}'