Supply GRC data from Advisor
This guide feeds a governance, risk and compliance (GRC) platform from Advisor. In five calls it finds a regulator, finds one of its standards, reads a paragraph verbatim, lists that standard's obligations one row per item, and checks what has changed since the platform last looked. Every row it stores carries a stable id, the drafter's headings, a citation and its verification against the authorised PDF, so the platform can show where each obligation comes from and when it moved.
The worked example is APRA's CPS 230 Operational Risk Management and its business continuity obligations. The same calls work for any regulator list_issuing_authorities returns: AASB and AUASB standards are held by paragraph in the same way; ASIC instruments and Acts are held by section.
Before you begin
- Python 3.10 or later and the official SDK:
pip install mcp. - A partner API key in
LAWATLAS_API_KEY, on an AI Pro account. See Get a credential.
Steps
- Find the regulator with
list_issuing_authorities. Itscodeis what the other tools take, andin_force_titlessays how many of its titles Advisor holds. - Find the standard with
search_legislation, passingissuing_authorityas a list and the standard's code as thequery. Match onstandard_code, not on the title: a prudential standard's registered title is a determination's name ("Banking, Insurance, ... determination No. 1 of 2026"), and its code is what a reader knows. - Read a paragraph with
get_provision_textandsection_numbersuch aspara 33. Keep itsanchor(the verification),status.compilation(which registered text it is) andlink(the citation) with the text. - List the obligations with
list_obligations. A paragraph's list of duties is one row per item, each with the lead-in ("An APRA-regulated entity must:"). Narrow withtopic(words in the headings) andverified_only, and page withnext_cursoruntil it isnull. Storeobligation_id; it is stable while the paragraph stays in place. - Stay current. Ask about the ids you stored with
list_obligations(obligation_id=[...]): each isactive,superseded(with the id that holds its words now),removed, or not found.list_recentwith the regulator gives its newest titles, andget_version_changeson a standard says whether it re-made an earlier one and which paragraphs changed.
import asyncio
import json
import os
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
URL = os.environ.get("LAWATLAS_MCP_URL", "https://advisor.lawatlas.ai/api/mcp-http/mcp")
class AdvisorError(Exception):
def __init__(self, error):
super().__init__(f"{error['code']}: {error['message']}")
self.code = error["code"]
async def call(session, tool, arguments):
"""Call one tool and return its JSON, raising on an error envelope."""
result = await session.call_tool(tool, arguments)
text = result.content[0].text
try:
body = json.loads(text)
except ValueError:
raise RuntimeError(text) # plain text: a lapsed subscription or a malformed argument
if isinstance(body, dict) and "error" in body:
raise AdvisorError(body["error"])
return body
async def all_obligations(session, arguments):
"""Every row for these arguments, page by page, until next_cursor is null."""
rows, cursor = [], None
while True:
page = await call(session, "list_obligations", {**arguments, **({"cursor": cursor} if cursor else {})})
rows += page["items"]
cursor = page["next_cursor"]
if not cursor:
return rows, page["counts"], page["not_listed"]
async def supply(regulator, standard_code, paragraph, topic, stored_ids):
headers = {"Authorization": f"Bearer {os.environ['LAWATLAS_API_KEY']}"}
async with streamablehttp_client(URL, headers=headers) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
# 1. The regulator, by the code the other tools take.
authorities = await call(session, "list_issuing_authorities", {})
reg = next((a for a in authorities["items"] if a["code"] == regulator), None)
if reg is None:
raise SystemExit(f"No regulator with code {regulator}")
print(f"{reg['code']}: {reg['name']}, {reg['in_force_titles']} titles in force")
# 2. The standard, by its code, among that regulator's titles.
found = await call(session, "search_legislation", {
"issuing_authority": [regulator], "query": standard_code, "limit": 3})
std = next((i for i in found["items"] if i.get("standard_code") == standard_code), None)
if std is None:
raise SystemExit(f"No {regulator} title with standard code {standard_code}")
print(f"{std['standard_code']} is {std['title_id']}: {std['title']}")
# 3. One paragraph, verbatim, with its verification and citation.
para = await call(session, "get_provision_text", {
"legislation_id": std["title_id"], "section_number": paragraph})
comp = para["status"]["compilation"]
print(f"\n{standard_code} {para['pinpoint']} ({para['anchor']['label']}, "
f"{comp['register_id']}, state {comp['state']}):")
print(para["text"])
print(f"Cite: https://advisor.lawatlas.ai{para['link']['url']}")
# 4. Its obligations on a topic, one row per item, verified only.
rows, counts, not_listed = await all_obligations(session, {
"title_id": std["title_id"], "topic": topic, "verified_only": True, "limit": 50})
print(f"\n{counts['total']} verified obligations under headings containing {topic!r}:")
for r in rows[:6]:
lead = (r.get("lead_in") or {}).get("text")
print(f" {r['obligation_id']} {r['pinpoint']} {(lead + ' ') if lead else ''}{r['text']}")
if len(rows) > 6:
print(f" ... {len(rows) - 6} more")
print(f"Not listed in this title: {not_listed}")
# 5. Staying current: the ids you stored, the regulator's new titles, and a re-make.
lookup = await call(session, "list_obligations", {"obligation_id": stored_ids})
for item in lookup["lookup"]:
print(f"\nStored {item['obligation_id']}: {item['status']}")
for missing in lookup.get("not_found") or []:
print(f"Stored {missing}: not found, never issued")
recent = await call(session, "list_recent", {"issuing_authority": [regulator], "limit": 3})
print(f"\nNewest {regulator} titles:")
for i in recent["items"]:
print(f" {i['registered_date']} {i.get('standard_code') or '-'} {i['title_id']} {i['title'][:70]}")
change = await call(session, "get_version_changes", {"legislation_id": std["title_id"], "limit": 3})
if change.get("transition") == "remake":
pred = change["predecessor"]
print(f"\n{standard_code} re-made: replaces {pred['title_id']} "
f"({pred['in_force']['frl_status']} since {pred['in_force']['since']}); "
f"paragraph changes {change['summary']}")
if __name__ == "__main__":
asyncio.run(supply(
regulator="APRA", standard_code="CPS 230", paragraph="para 33", topic="business continuity",
stored_ids=["F2026L00475/main/para_33/GRC01", "F2023L01242/main/para_33/GRC01"]))
Check it worked
Run python grc_supply.py. Run on 24 October 2026 against a local Advisor holding the same corpus, it printed:
APRA: Australian Prudential Regulation Authority, 386 titles in force
CPS 230 is F2026L00475: Banking, Insurance, Life Insurance, Health Insurance and Superannuation (prudential standard) determination No. 1 of 2026
CPS 230 para 33 (Verified, F2026L00475, state current):
An APRA-regulated entity must:
(a) define, identify and maintain a register of its critical operations;
(b) take reasonable steps to minimise the likelihood and impact of disruptions to its critical operations;
(c) maintain a credible BCP that sets out how it would maintain its critical operations within tolerance levels through disruptions, including disaster recovery planning for critical information assets;¹²
(d) activate its BCP if needed in the event of a disruption; and
(e) return to normal operations promptly after a disruption is over.
12 An entity may have a number of BCPs. A BCP may include separate crisis management plans and disaster recovery plans.
Cite: https://advisor.lawatlas.ai/l/F2026L00475/as_made/main/para_33
12 verified obligations under headings containing 'business continuity':
F2026L00475/main/para_33/GRC01 para 33(a) An APRA-regulated entity must: define, identify and maintain a register of its critical operations;
F2026L00475/main/para_33/GRC02 para 33(b) An APRA-regulated entity must: take reasonable steps to minimise the likelihood and impact of disruptions to its critical operations;
F2026L00475/main/para_33/GRC03 para 33(c) An APRA-regulated entity must: maintain a credible BCP that sets out how it would maintain its critical operations within tolerance levels through disruptions, including disaster recovery planning for critical information assets;¹²
F2026L00475/main/para_33/GRC04 para 33(d) An APRA-regulated entity must: activate its BCP if needed in the event of a disruption; and
F2026L00475/main/para_33/GRC05 para 33(e) An APRA-regulated entity must: return to normal operations promptly after a disruption is over.
F2026L00475/main/para_39/GRC01 para 39(a) An APRA-regulated entity’s BCP must include: the register of critical operations and associated tolerance levels;
... 6 more
Not listed in this title: {'titles': 1, 'titles_without_sections': 0, 'no_stable_address': 0, 'not_separable': 0, 'stopped_amending_text': 0, 'pending_reextraction_on_this_page': 0}
Stored F2026L00475/main/para_33/GRC01: active
Stored F2023L01242/main/para_33/GRC01: not found, never issued
Newest APRA titles:
2026-10-06 ARS 222.0 F2026L01360 Financial Sector (Collection of Data) (reporting standard) determinati
2026-10-06 ARS 221.0 F2026L01358 Financial Sector (Collection of Data) (reporting standard) determinati
2026-09-25 GRS 112.3 F2026L01291 Financial Sector (Collection of Data) (reporting standard) determinati
CPS 230 re-made: replaces F2023L01242 (Repealed since 2026-07-01); paragraph changes {'added': 10, 'removed': 32, 'amended': 9, 'heading_changed': 1, 'moved': 56, 'ambiguous': 5}
What to notice:
- The paragraph and its rows are verified against the authorised PDF of F2026L00475, and the citation resolves to that registered text. The footnote marker
¹²and footnote 12 are printed in the standard; the paragraph'sanchorsays the footnote is not part of its verification. - Para 33's five duties are five rows sharing one lead-in, so each can be assigned, evidenced and tracked on its own.
not_listedis all zeros excepttitles: 1(the one title asked about): nothing in this standard was held back from the list.- The stored CPS 230 (2023) id is not found. CPS 230 was re-made in 2026: F2026L00475 replaced F2023L01242, which was repealed from 1 July 2026 and so never had obligations listed. A re-made standard's obligation ids are new. Map old to new with
get_version_changes, which gives a hash of each changed paragraph's old text and the predecessor's Federal Register link, not its text.
What to do if it did not
| Problem | Solution |
|---|---|
AdvisorError: FORBIDDEN from list_obligations | The key's organisation owner has no active AI Pro subscription; renew it |
RuntimeError: ... Input should be a valid list | Pass issuing_authority as a list: ["APRA"] |
No APRA title with standard code ... | No title of that regulator carries that standard_code; try search_legislation with words from the standard's name |
| HTTP 401 | Check LAWATLAS_API_KEY; see Troubleshooting |
What a row claims
A row says only that the provision's own text says "must" (or "must not", "shall", "is required to", "commits an offence"), quoted, under these headings, in this title, made by this regulator. No model reads or classifies it, and it is never a statement of whom the provision applies to or what it requires of a particular entity. That judgement stays with the GRC platform and its users. See list_obligations for the flags and penalties a row can carry.
Next steps
list_obligations: every argument, flag and errorget_version_changes: what a re-make changed, paragraph by paragraph- Cite Advisor output correctly