Ontario law your agent can call.
Every tool on mithril.law answers over MCP, REST or one URL, with each step cited and the answer signed. When a matter needs a lawyer, your agent hands it to me.
Run a tool from this page
Severance check
CAD 38,960Your minimum under the Act
Your minimum under Ontario’s Employment Standards Act is CAD 38,960: 8 weeks of termination pay, 11 weeks of severance pay, and vacation pay on the termination pay. At common law, courts have given people with similar service, age and role roughly 9 to 15 months’ pay, about CAD 78,000 to CAD 130,000. That is an estimate, not a prediction. The last day to start a lawsuit is September 1, 2028.
- Length of service10 years, 11 monthsEmployment Standards Act, 2000, s. 57Employment Standards Act, 2000, s. 65(1)
- Regular weekly wageCAD 2,000Employment Standards Act, 2000, s. 1(1), “regular wages”
- Termination pay8 weeks · CAD 16,000Employment Standards Act, 2000, s. 54Employment Standards Act, 2000, s. 57(h)Employment Standards Act, 2000, s. 61(1)(a)
- Vacation pay on termination payCAD 960Employment Standards Act, 2000, s. 35.2(b)Employment Standards Act, 2000, s. 1(1), “wages”Panaligan v. Mayfair Tennis Courts Limited, 2017 CanLII 9126 (ON SCSM), paras. 55-56Rainbow Concrete Industries Limited v. Lentir, 2012 CanLII 58233 (ON LRB), para. 33
- Severance pay11 weeks · CAD 22,000Employment Standards Act, 2000, s. 64(1)Employment Standards Act, 2000, s. 65(1)Employment Standards Act, 2000, s. 65(4)Rainbow Concrete Industries Limited v. Lentir, 2012 CanLII 58233 (ON LRB), para. 33Employment Standards Act, 2000, s. 64(2)Hawkes v. Max Aicher (North America) Limited, 2021 ONSC 4290 (Div. Ct.), paras. 51-52
automated
Done by the software. Nothing waits on the person or a lawyer.
An example on fictional facts. This is legal information, not legal advice. Using the software does not make you my client.
Endpoints
Checking the service…
- MCP server
https://mithril.law/mcp- One URL
https://mithril.law/{area}/{tool}.md?…How- REST
https://mithril.law/api/v1- OpenAPI 3.1
- /openapi.json
- For agents
- /llms.txt
- Signing key
- /.well-known/jwks.json
34 tools: 23 operations and 11 for handoffs, bookings and checking the law. Listing them needs no sign-in, and the free ones run without it. API v1, surface version 1.0.0.
The software stops. Your agent hands the matter to me.
A criminal charge needs a lawyer, so the answer comes back needs_lawyer with the calls to make next. This is what a well-behaved agent does with it.
The personto your agent
My brother was arrested last night and he is still at the police station. What happens now?
Your agentto mithril.law
{ "name": "criminal_what_happens_next", "arguments": { "stage": "arrested_held", "youth": false } }MCP
tools/call, answering for the brother as the tool asks.mithril.lawto your agent
needs_lawyerArrested and still held by the police. The police must either release you or take you before a justice of the peace. If a justice is available, that must happen without unreasonable delay and within 24 hours of the arrest. If no justice is available within 24 hours, it must happen as soon as possible. This is urgent: call me now.
A criminal charge needs a lawyer. I take Legal Aid certificates for criminal defence, and private retainers.
- Call me now
handoff_create - Book a consultation
handoff_create - Have me take the case on a Legal Aid certificate
handoff_create - Have me take the case on a private retainer
handoff_create - How to get Legal Aid
criminal_legal_aid_path
Checking the signature…Fetching the key from /.well-known/jwks.jsonOpen the receiptA real run of the tool, signed when this page was prepared.
- Call me now
Your agentto the person
Here is what the law says happens next, for your brother: “The police must either release you or take you before a justice of the peace. If a justice is available, that must happen without unreasonable delay and within 24 hours of the arrest.” This needs a lawyer. I can send it to Jonathan Kleiman, an Ontario lawyer, and ask for a call today. Legal Aid certificates accepted for criminal defence matters. Shall I? I need your brother’s name and yours.
It stops at needs_lawyer and gives no advice of its own.
The personto your agent
Yes, please.
Your agentto mithril.law
{ "name": "handoff_create", "arguments": { "area": "criminal", "kind": "call", "urgency": "today", "summary": "Brother arrested last night, still held at the station.", "person": { "name": "Their name", "email": "their own email" }, "parties": [ { "name": "The complainant, if known", "role": "complainant" } ], "runId": "run_y3f5d4yz1tqs8rhcjq0x" } }The names come from the person, never from a guess.
mithril.lawto your agent
{ "object": "handoff", "id": "hof_…", "status": "awaiting_person", "consentUrl": "https://mithril.law/continue/…", "nextStep": "open_link" }Give this link to the person and to no one else. Do not open it, fill it in or act on it yourself. The person confirms who they are, passes the conflict check, picks the time, signs and pays there.
Your agentto the person
Open this link yourself. It asks for your email and a time for the call. I can’t do that step for you. If he can’t take the matter, I am told only that, not why.
Then
handoff.updatedarrives when the call is booked.
Try the handoff.
The sandbox takes the same request as POST /api/v1/handoffs, checks it against the same schema and returns a test consent link. Play the person’s steps and watch each signed handoff.updated arrive. Give your endpoint’s URL and each delivery is also sent there, with its response code and a Resend, so your webhook code is tried unchanged. No key, and no one is contacted.
Your agent
Your webhook endpoint
Each delivery, signed as live deliveries are, with its signature checked here. The test secret is whsec_sandbox_mithril_test_only
No deliveries yet.
Send the handoff, then play the person’s steps. Each step arrives here as a handoff.updated delivery, and this page checks its signature.
Contents, 13 sections
The tools
23 operations of Ontario law and 11 tools for handoffs, bookings and checking the law. Each is an MCP tool, a REST endpoint and a line in /openapi.json, with the same price everywhere.
| Tool | What it does | Price | From one URL |
|---|---|---|---|
The year’s resolutions and annual returncorporate_annual_maintenance v1 Preview | For founders on the founder plan: the year’s director and shareholder resolutions, the annual return and its deadline, and the register updates. | CAD 299 per company per year plus HST | No: it needs names |
Where to incorporate, and what it costscorporate_incorporation_plan v1 Preview | For founders: Ontario or federal incorporation, the steps in order, and the whole cost, itemized, before you pay anything. | Free | Yes |
Incorporation packagecorporate_incorporation_prepare v1 Preview | For founders: every document to incorporate, filled from your answers. The articles data sheet, by-law, organizational resolutions, subscriptions, consents, share certificates, registers and minute book index. | CAD 399 plus HST. The government filing fee is extra. | No: it needs names |
Register of individuals with significant controlcorporate_isc_register v1 Preview | For founders on the founder plan: who has significant control under the 25% tests, and the register ready to keep. | CAD 299 per company per year plus HST | No: it needs names |
Issue sharescorporate_share_issuance v1 Preview | For founders on the founder plan: the directors’ resolution to issue shares, the subscriptions and certificates, and the updated registers. Anything that needs a lawyer is flagged. | CAD 299 per company per year plus HST | No: it needs names |
How urgent is a criminal mattercriminal_intake_triage v1 Preview | From custody and court dates only: how fast to reach me, and whether to call now or book a consultation. No legal conclusions. | Free | Yes |
Getting a legal aid lawyer for a criminal chargecriminal_legal_aid_path v1 Preview | How to apply to Legal Aid Ontario for a certificate, what a certificate is, and what to send me once you have one. Legal Aid certificates accepted for criminal defence matters. | Free | Yes |
Someone was arrested or charged: what happens nextcriminal_what_happens_next v1 Preview | For an accused person or their family: what happens next in order, your rights at this stage, what to do and not do, and how to get a lawyer, every line cited. | Free | Yes |
Demand letteremployment_demand_letter v1 Preview | A demand letter from an Ontario employee to their former employer, with the ESA sections and the common-law claim, as DOCX and PDF. The person signs and sends it. | CAD 149 plus HST | No: it needs names |
Deadlines after a job endsemployment_limitation_dates v1 Preview | The last day to sue for wrongful dismissal and the last day to file an ESA complaint in Ontario, with the choice between them. | Free | Yes |
Severance checkemployment_severance_check v1 Preview | What an Ontario employee is owed when a job ends: the ESA minimum, an estimate of the common-law range, the employer’s offer against both, and the deadlines. | Free | Yes |
Termination clause checkemployment_termination_clause_check v1 Preview | Checks the wording of an Ontario employment contract’s termination clause for defects the Court of Appeal has recognized. | Free | Yes |
How long you have to sue in Ontariogeneral_limitation_period v1 Preview | The Limitations Act, 2002 deadlines for a court claim: two years from discovery and 15 years from the day it happened, with the common exceptions flagged for a lawyer. | Free | Yes |
Where to start with a legal problem in Ontariogeneral_triage v1 Preview | The front door: from what happened and a few dates, which Mithril operations fit, how urgent it is, and the time limit to sue where one may apply. | Free | Yes |
Prepare an L1 to evict for non-payment of rentltb_l1_prepare v1 Preview | Checks the landlord may file, adds up rent owing to the filing date, NSF charges and the fee, fills the official L1, and explains how to file it. | CAD 1 per unit per month, minimum CAD 49, plus HST | No: it needs names |
An N4 for every unit in arrearsltb_n4_bulk v1 Preview | One N4 for each unit in arrears across a rent roll, the batch in one table, and what to fix for any unit that needs it. | CAD 1 per unit per month, minimum CAD 49, plus HST | No: it needs names |
Prepare an N4 for non-payment of rentltb_n4_prepare v1 Preview | Checks what the N4 may claim, adds up the rent owing, sets the termination date, and fills the official N4 for the landlord to sign and serve. | CAD 1 per unit per month, minimum CAD 49, plus HST | No: it needs names |
LTB orders by applicationltb_outcomes v1 Preview | How many orders the Board issued for an application, by kind of order, month and filing year, from the LTB’s open data. | Free | Yes |
Is this rent increase lawful?ltb_rent_increase_check v1 Preview | Checks an increase against the guideline, the 12-month rule, the 90 days’ written notice and the exemption for newer units, then says what each side can do. | Free | Yes |
Which LTB application fits a tenant’s problemltb_tenant_path v1 Preview | Which application fits a tenant’s problem (T1, T2, T5 or T6), the deadline for each, the fee, what to gather and the facts the form asks for. | Free | No: it needs names |
Termination date for a noticeltb_termination_date v1 Preview | The earliest valid termination date for an N4, N5, N8, N12 or N13, when the notice counts as given, and when the landlord can apply. | Free | Yes |
Small claims: is it the right court, and where to gosmall_claims_route v1 Preview | Checks whether an amount is within the Small Claims Court’s limit, and links to makethempay.ca, which opens the claim, with the amount filled in. | Free | Yes |
Open a small claims case at makethempay.ca from an invoicesmall_claims_start_claim v1 | Forwards an unpaid invoice to makethempay.ca’s Claims API, which opens the case and prepares a dated demand letter. Returns makethempay.ca’s own answer. | Free | No: it needs names |
Hand the matter to Jonathan Kleiman (lawyer)handoff_create | Ask Mithril Law (Jonathan Kleiman, Ontario lawyer) to take on the person’s matter. | Free | No: POST or MCP |
Handoff statushandoff_status | Where a handoff stands: awaiting_person, conflict_check, referred, booked, engagement_sent, engaged, matter_open, declined or cancelled. | Free | No: POST or MCP |
Open consultation and call timesslots_list | My real availability, so you can offer the person a time in the same conversation. | Free | No: POST or MCP |
Hold a time for the personbooking_hold | Hold a consultation or call time from slots_list for the person. | Free | No: POST or MCP |
Verify a receiptreceipt_verify | Check a run’s signed receipt (the `receipt.jws` of any run): whether the signature is Mithril’s, what the answer relied on (operation, rule versions and hashes, verification status then and now) and whether the answer has been superseded because the law changed. | Free | No: POST or MCP |
Search the verified rulesrules_search | Search the rules behind the operations: each is one unit of Ontario law as the software implements it, with its sources (e-Laws, Justice Laws, Tribunals Ontario, the courts) and its verification status. | Free | No: POST or MCP |
Get a rulerule_get | One rule in full: the statement exactly as the code implements it, its sources with URLs, the worked examples a lawyer checked, its status and the operations that use it. | Free | No: POST or MCP |
Pricescatalog | Every price, as shown on the site: software (sold by Jonathan Kleiman), legal services (Mithril Law) and consulting. | Free | No: POST or MCP |
Quote a fixed-fee engagement with Mithril Lawretainer_quote | A signed quote for one defined service from Mithril Law (Jonathan Kleiman, Ontario lawyer): what I do, what I don’t, what the person must do, how soon, and the fee from the catalog with HST. | Free | No: POST or MCP |
Retain Mithril Law for the person, under their mandateretainer_retain | Retain Mithril Law for the person on a quote from retainer_quote, under their standing mandate. | Free | No: POST or MCP |
Standing mandate statusmandate_status | The person’s standing mandates for you: whether each is pending, active, expired, revoked or replaced; the services it covers; the limits and what is left this month; whether their identity is complete (then I can accept without waiting); and every use. | Free | No: POST or MCP |
MCP
The server is at https://mithril.law/mcp (Streamable HTTP). It speaks protocol 2026-07-28 and, for older clients, 2025-11-25, 2025-06-18, 2025-03-26. Listing the tools never needs sign-in, and the free ones run without it.
Add a custom connector in Claude with this URL.
https://mithril.law/mcpIn ChatGPT, turn on developer mode, then add a connector with this URL.
https://mithril.law/mcpOne command in your terminal.
claude mcp add --transport http mithril https://mithril.law/mcpAdd to Cursor or paste this into .cursor/mcp.json.
{
"mcpServers": {
"mithril": {
"url": "https://mithril.law/mcp"
}
}
}Add to VS Code or paste this into .vscode/mcp.json.
{
"servers": {
"mithril": {
"type": "http",
"url": "https://mithril.law/mcp"
}
}
}import {
Client,
StreamableHTTPClientTransport,
} from "@modelcontextprotocol/client";
const client = new Client(
{ name: "my-agent", version: "1.0.0" },
{ versionNegotiation: { mode: "auto" } },
);
await client.connect(
new StreamableHTTPClientTransport(
new URL("https://mithril.law/mcp"),
),
);
const { tools } = await client.listTools();
const run = await client.callTool({
name: "employment_severance_check",
arguments: {
"endedHow": "let_go_without_cause",
"endDate": "2026-09-01",
"yearsOfService": 10,
"monthsOfService": 11,
"age": 45,
"role": "staff",
"pay": { "kind": "salary", "annualSalary": 104000 },
"payroll": "yes",
"hasBenefits": true,
"terminationClause": "no",
"exemptions": [],
"workingNoticeWeeks": 0,
"massTermination": "no",
"businessClosure": "no"
},
});
console.log(run.structuredContent);import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
async def main():
async with streamablehttp_client(
"https://mithril.law/mcp"
) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
print([t.name for t in tools.tools])
asyncio.run(main())- Tools
- One per operation, named after it with dots as underscores (
employment_severance_check), plushandoff_create,handoff_status,slots_list,booking_hold,receipt_verify,rules_search,rule_get,catalog,retainer_quote,retainer_retainandmandate_status. - Resources
mithril://rules/{id},mithril://sources/{id},mithril://receipts/{id}andmithril://catalog.- Prompts
i_was_let_go,i_got_an_n4,someone_was_arrestedandstarting_a_company.- Missing facts
- Declare form elicitation in your client’s capabilities and a call with facts missing comes back
input_required(2026-07-28), with a form asking exactly what the law needs. Show it to the person, then call again with their answers ininputResponsesand therequestStateyou were given. The playground shows the exchange: choose MCP and clear a fact. An older client gets an error naming each missing fact. Ask the person; never fill one in yourself.
One URL
An agent that can only fetch a URL gets the same answer. Add .md to a tool’s address and put the facts in the query string. The answer comes back as markdown: the summary, each step with its section, the boundary and the receipt.
https://mithril.law /employment /severance-check.md ?endedHow=let_go_without_cause &endDate=2026-09-01 &yearsOfService=10 &monthsOfService=11 &age=45 &role=staff &pay.kind=salary &pay.annualSalary=104000 &payroll=yes &hasBenefits=true &terminationClause=no &workingNoticeWeeks=0 &massTermination=no &businessClosure=no- Facts go in as dotted parameters (
pay.annualSalary=104000), lists as repeated parameters, or all at once asfacts=<url-encoded JSON>. - If a fact is missing, nothing is run. The answer names each missing fact, with an example, and gives the URL to fetch next.
- Names and addresses never go in a URL. Tools that need them say how to go on: by POST, by MCP, or on their page.
- /llms.txt lists a working URL for every tool. Every page on the site has a markdown version: add
.md, or ask fortext/markdown.
REST
Every run returns the answer, a summary you can repeat, the steps with their citations, the boundary, the law it relied on and a signed receipt. Send an Idempotency-Key and say who you are with Mithril-Agent.
curl -X POST \
https://mithril.law/api/v1/operations/employment.severance_check/runs \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Mithril-Agent: my-agent/1.0" \
-d '{
"input": {
"endedHow": "let_go_without_cause",
"endDate": "2026-09-01",
"yearsOfService": 10,
"monthsOfService": 11,
"age": 45,
"role": "staff",
"pay": { "kind": "salary", "annualSalary": 104000 },
"payroll": "yes",
"hasBenefits": true,
"terminationClause": "no",
"exemptions": [],
"workingNoticeWeeks": 0,
"massTermination": "no",
"businessClosure": "no"
}
}'{
"error": {
"code": "invalid_input",
"message": "Some facts are missing or not in the right form.",
"hint": "…",
"details": [
{ "path": "endDate", "message": "…" }
]
},
"requestId": "req_…"
}Every operation, with its schema, price and verification status
OpenAPI 3.1, with a real example response for each
Typed clients
One file for TypeScript and one for Python, generated from /openapi.json: a method for each of the 23 operations, with its facts and its answer typed and its price in the doc comment. Neither needs a package: TypeScript uses fetch, Python the standard library.
curl -O https://mithril.law/agents/sdk/mithril.tsimport { Mithril } from "./mithril";
const mithril = new Mithril();
const run = await mithril.employmentSeveranceCheck({
"endedHow": "let_go_without_cause",
"endDate": "2026-09-01",
"yearsOfService": 10,
"monthsOfService": 11,
"age": 45,
"role": "staff",
"pay": { "kind": "salary", "annualSalary": 104000 },
"payroll": "yes",
"hasBenefits": true,
"terminationClause": "no",
"exemptions": [],
"workingNoticeWeeks": 0,
"massTermination": "no",
"businessClosure": "no"
});
console.log(run.result.summary, run.receipt.url);curl -O https://mithril.law/agents/sdk/mithril.pyfrom mithril import Mithril
mithril = Mithril()
run = mithril.employment_severance_check({
"endedHow": "let_go_without_cause",
"endDate": "2026-09-01",
"yearsOfService": 10,
"monthsOfService": 11,
"age": 45,
"role": "staff",
"pay": { "kind": "salary", "annualSalary": 104000 },
"payroll": "yes",
"hasBenefits": True,
"terminationClause": "no",
"exemptions": [],
"workingNoticeWeeks": 0,
"massTermination": "no",
"businessClosure": "no"
})
print(run["result"]["summary"], run["receipt"]["url"])A missing fact comes back as MithrilError with code invalid_input and the facts to ask for in missing. Each file names the surface version it was built from; download it again when a tool’s version changes.
The boundary
Every answer says where the software stopped, and lists the next steps as actions you can call.
automatedDone by the software. Nothing waits on the person or a lawyer.
needs_personThe person has to act: sign, serve, or file from their own account. Pass the steps to them.
needs_lawyerStop. Don’t answer the legal question yourself. Offer to hand the matter to me.
The handoff
POST /api/v1/handoffs (MCP handoff_create) with the area, a summary in the person’s words, the person, everyone on the other side and the run. You get a consent link for the person. I check the names against my practice records and this site’s records. If there is a conflict, the person is referred elsewhere and you never learn why.
- 1. Your agent to mithril.law
POST /api/v1/handoffsarea, summary, the person, everyone on the other side, runId - 2. mithril.law to your agent
201 · awaiting_personid and consentUrl - 3. Your agent to the personThe consent linkGiven to the person, and to no one else
- 4. The person to mithril.lawOpens it and confirms their emailwith a code sent to that address
- 5. mithril.lawConflict check against my practice records and this site’s recordsA conflict ends it here: referred, with no details
- 6. The person to mithril.lawPicks a timeand pays, if the consultation is priced
- 7. mithril.law to your agent
handoff.updated · bookedwebhook, or poll GET /api/v1/handoffs/{id} - After the consultation, if I take the matter on
- 8. mithril.law to the personEngagement letter, or the LSO contingency formThe person confirms who they are and signs
- 9. The person to mithril.lawSigns, and pays any retainer into trust
- 10. mithril.law to your agent
engaged · matter_openwith the portal link, if the person gave you matter:read
{
"area": "employment",
"kind": "consultation",
"summary": "Let go after 10 years, offered 8 weeks.",
"person": {
"name": "…",
"email": "…"
},
"parties": [
{ "name": "…", "role": "employer" }
],
"runId": "run_…"
}{
"object": "handoff",
"id": "hof_…",
"status": "awaiting_person",
"consentUrl": "https://mithril.law/continue/…",
"agentInstructions": "Give this link to the person …"
}Standing mandates are not open yet. Until they are, every matter goes through the handoff.
- Once, with the person
- 1. Your agent to the personA link to set up a mandate
- 2. The person to mithril.lawConfirms who they are and signs the mandateIt names the agent, the kinds of engagement, a fee cap and a payment method
- Then, for each matter, within those limits
- 3. Your agent to mithril.law
retainer_quotea signed quote: scope, price, what is left out - 4. mithril.lawConflict checkclear, needs review, or can’t act
- 5. Your agent to mithril.law
retainer_retainaccepted under my acceptance policy, or sent to me - 6. mithril.law to the personThe engagement letter, copied to them
- 7. mithril.law to your agent
matter_open
Always with the person, mandate or not: confirming who they are, contingency agreements, anything outside the mandate, and the decisions a client must make.
Receipts
Every run is signed with the site’s Ed25519 key (JWS, EdDSA). The receipt holds hashes of the facts and the answer, not the facts. Anyone can check it against /.well-known/jwks.json, and the verification register shows each rule it names.
run- The run id. The receipt page is /r/{run}.
operation- The tool and its version.
input,output- SHA-256 of the facts and of the answer, in canonical JSON.
asOf- The date of the law it applied.
rules- Each rule with its version, hash and whether a lawyer had verified it.
boundary- Where the software stopped.
import {
compactVerify,
createRemoteJWKSet,
} from "jose";
const jwks = createRemoteJWKSet(
new URL("https://mithril.law/.well-known/jwks.json"),
);
const { payload } = await compactVerify(
receipt.jws,
jwks,
);
const { mithril } = JSON.parse(
new TextDecoder().decode(payload),
);When a rule changes, earlier runs that used it are marked superseded and you get run.superseded. Don’t repeat a superseded answer: run the operation again. POST /api/v1/receipts/verify answers all of this in one call.
Webhooks
POST /api/v1/webhooks with an API key, a public https URL and the events you want. The secret is shown once. Each delivery is signed Mithril-Signature: t=<unix>,v1=<hex>, an HMAC-SHA256 of t.rawBody. Reject anything older than five minutes.
import { createHmac, timingSafeEqual } from "node:crypto";
// secret: the whsec_… value shown once when you created the endpoint.
// header: the Mithril-Signature request header. body: the raw request body, as a string.
export function verifyMithrilSignature(secret, header, body, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.trim().split("=", 2)));
const t = Number(parts.t);
if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret).update(`${t}.${body}`).digest("hex");
return header.split(",").map((p) => p.trim()).filter((p) => p.startsWith("v1=")).some((p) => {
const given = Buffer.from(p.slice(3));
return given.length === expected.length && timingSafeEqual(given, Buffer.from(expected));
});
}import hmac, hashlib, time
def verify_mithril_signature(secret: str, header: str, body: bytes, tolerance: int = 300) -> bool:
parts = dict(p.strip().split("=", 1) for p in header.split(","))
t = int(parts.get("t", "0"))
if abs(time.time() - t) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + body, hashlib.sha256).hexdigest()
given = [p.strip()[3:] for p in header.split(",") if p.strip().startswith("v1=")]
return any(hmac.compare_digest(g, expected) for g in given)The 13 events, and when each is sent
| Event | Sent when |
|---|---|
run.completed | A run you made finished. Carries the run id, operation, boundary and links. |
run.superseded | A rule a run of yours relied on changed after the run. Stop repeating that answer; run it again. |
rule.changed | A rule changed (amendment, practice direction or fee change) and is being re-verified. |
handoff.updated | A handoff you created changed status (booked, engaged, matter_open, referred…). |
booking.updated | A booking you held was confirmed, moved or cancelled. |
matter.updated | Delegated access only: the person’s matter changed (new shared document, message, date). |
document.ready | A document you were waiting for (for example after payment) is ready to download. |
journey.updated | A supervised journey across several operations moved on: a step finished, a date arrived, or it needs the person. |
consulting.proposal.sent | Consulting: a proposal was sent to your organization. |
consulting.proposal.accepted | Consulting: your organization accepted a proposal. |
consulting.project.updated | Consulting: a project for your organization changed stage. |
consulting.deployment.updated | Consulting: your workspace deployment changed status. |
principal_step.completed | The person finished a step only they can take (identity, a signature, a decision). Carry on from where you left off. |
Any 2xx acknowledges a delivery. Anything else is retried at 1, 2, 4, 8, 16, 32 and 64 minutes: 8 attempts in all. The sandbox above signs its test deliveries the same way.
Sign-in
| You are | Send |
|---|---|
| Trying a free tool, or the sandbox | Nothing. Listing tools over MCP never needs sign-in either. |
| A developer or an organization | An API key: Authorization: Bearer mk_…, made at /account/keys. Runs are recorded to your account and metered. |
| Acting for a person | Their personal access token: an mk_… key they make for their own agent, with the scopes they choose. Or OAuth 2.1 with PKCE; they can revoke either at /account/agents. |
The 8 scopes, and what each lets an agent do
| Scope | Lets the agent |
|---|---|
runs:read | See the runs made for you and their receipts. |
runs:write | Run operations for you. |
handoffs:write | Ask Mithril Law to take on your matter. You still confirm, sign and pay yourself. |
bookings:write | Hold a consultation or call time for you to confirm. |
matter:read | See your matter as the client portal shows it: status, dates, your tasks, shared documents and messages. |
matter:documents:write | Upload documents to your matter. |
matter:messages:write | Send messages to me on your matter. |
billing:read | See invoices, payments and Legal Aid certificate status on your matter. |
No scope reaches Crown disclosure, notes, drafts, research, time entries or conflict data.
Mithril-Agent: my-agent/1.0
Mithril-Agent-Model: provider/model
Mithril-On-Behalf-Of: personPrices
| API and agent runs | CAD 1 per run, first 100 free each month, plus HST |
|---|---|
| Demand letter package | CAD 149 plus HST |
| Incorporation | CAD 399 plus HST. The government filing fee is extra. |
| Founder plan | CAD 299 per company per year plus HST |
| Landlord plan | CAD 1 per unit per month, minimum CAD 49, plus HST |
A paid step comes back with a payment object and a checkout link for the person. Until they pay, fetching the document answers 402. Runs from a URL without a key are not metered.
Rate limits, per minute
| Caller | Reads | Runs | Writes |
|---|---|---|---|
| No key, per IP address | 120 | 30 | 10 |
| API key or token | 1,200 | 300 | 60 |
Versions and changes
- The API
- REST is at
/api/v1, and /api/v1 names the surface version, now 1.0.0. MCP clients agree a protocol version when they connect. - Each tool
- Has a version number, shown in the table above. Every receipt signs the version that answered, so an answer can always be traced to the code that gave it.
- The law
- Each rule has a version and a hash of its text, sources and worked examples. Change any of them and my sign-off no longer applies: the tools that use the rule stop until I verify it again. The register shows each rule’s record.
- Follow the changes
- The register’s feed, /verify/feed.xml (Atom), lists each rule verified and each rule sent back for a new check. Over webhooks:
rule.changedandrun.superseded. Status and changes shows the service checked now, its scheduled jobs over 30 days and the same changes.
Rules for agents
- Never invent facts. Ask the person for every fact an operation needs. When a tool asks for missing facts, ask the person exactly those questions.
- Consent links and checkout links go to the person, and only to the person. Never open them, fill them in, sign or pay through them yourself.
- The boundary is an instruction.
automatedmeans the job is done.needs_personmeans the person must act (sign, serve, file from their own account).needs_lawyermeans stop: do not answer the legal question yourself; offer the handoff. - Cite the receipt when you repeat an answer. If the answer is old, check the receipt: a superseded run means the law behind it changed.
- Quote only the prices the catalog gives. Never promise an outcome.
The software provides legal information and document preparation, sold by Jonathan Kleiman. Legal advice starts at the lawyer boundary and is provided by Mithril Law under an engagement.