RAG · agents · retrieval

Ground an AI agent in real AI-law data

A model asked about AI regulation will produce a fluent answer whether or not it knows one. Retrieval does not make a model truthful — but it does give you an answer you can check, because every claim can be traced back to a record with an official government URL attached.

Published · AI Law Tracker Editorial Team

Get a free keyAPI docs

What grounding does and does not buy you

Be precise about the mechanism, because the marketing around this topic is not. Retrieval-augmented generation does not eliminate fabrication. What it changes is where the facts come from and whether a reader can audit them.

  • It moves the claim from memory to evidence. The model is answering from a payload it was just handed, not from a statute it half-remembers from pre-training.
  • It makes an answer checkable. Every retrieved record carries official_url — a primary/official (.gov) link where one exists. A wrong answer becomes a *detectable* wrong answer.
  • It gives you a refusal path. When retrieval returns nothing, that is a fact about coverage, and your prompt can require the model to say so instead of improvising.
  • It does not stop a model from mis-summarising what it was given. That is a separate risk, mitigated by short quoted spans, mandatory citations, and keeping the verbatim source field in the context.
The dataset itself is deterministic — the retrieval path contains no model, so it cannot invent a statute, a deadline or a penalty. The generation step still can. Design for that asymmetry rather than claiming it away.

Step 1 — retrieval

GET /v1/search is the entry point: text search over title and summary, newest-first by default, with the same filters as /v1/laws. Narrow with jurisdiction and scope where the user’s question implies them — a question about California should not retrieve Colorado records.

Terminal
curl -s -G "https://ai-law-tracker.com/api/v1/search" \
  -H "X-API-Key: alt_free_your_key_here" \
  --data-urlencode "q=automated employment decision" \
  --data-urlencode "jurisdiction=illinois" \
  --data-urlencode "limit=10"
Python — a retrieval function that returns citations, not prose
import os
import requests

BASE = "https://ai-law-tracker.com/api/v1"
HEADERS = {"X-API-Key": os.environ["ALT_API_KEY"]}


def retrieve(question, jurisdiction=None, scope=None, limit=10):
    """Return (passages, citations). Never returns prose — only retrieved rows."""
    params = {"q": question, "limit": limit}
    if jurisdiction:
        params["jurisdiction"] = jurisdiction
    if scope:
        params["scope"] = scope

    r = requests.get(f"{BASE}/search", params=params, headers=HEADERS, timeout=30)
    r.raise_for_status()
    payload = r.json()

    passages, citations = [], []
    for row in payload["data"]:
        passages.append(
            "\n".join(
                [
                    f"[{row['id']}] {row.get('identifier') or ''} — {row.get('title') or ''}",
                    f"Jurisdiction: {row['jurisdiction'].get('name') or row['jurisdiction']['slug']} ({row['scope']})",
                    f"Status (verbatim from source): {row.get('status') or 'not stated'}",
                    f"In force: {row.get('in_force')}",   # null means the source did not say
                    f"Summary: {row.get('summary') or 'not available'}",
                    f"Official source: {row.get('official_url') or 'no official URL on record'}",
                    f"Last changed: {row.get('updated_at')} | Last verified: {row.get('checked_at')}",
                ]
            )
        )
        citations.append(
            {
                "id": row["id"],
                "label": row.get("identifier") or row.get("title"),
                "official_url": row.get("official_url"),
                "attribution": row.get("attribution"),
            }
        )

    return passages, citations, payload["meta"]

Step 2 — the system prompt that does the work

The retrieval is the easy half. The constraints below are what keep a fluent model from smoothing over a gap in the evidence.

  • Rule 5 is the one people cut, and it is the one that matters. An empty retrieval is a real answer about coverage — let the agent give it.
  • Rule 3 exists because effective dates in this domain are frequently phrased, not stated. Preserving the source’s own vagueness is more useful than resolving it incorrectly.
  • Rule 4 exists because in_force is set only where the source states it explicitly, and is null otherwise. A model that reads null as false will confidently tell a user a law is not yet effective.
System prompt
You answer questions about AI regulation using ONLY the RETRIEVED RECORDS below.

Rules:
1. Every factual claim must come from a retrieved record. If the records do not
   support a claim, do not make it.
2. Cite every claim inline with the record's official source URL. A sentence
   with no citation is not allowed.
3. Quote status and effective-date language verbatim from the record. Do not
   paraphrase a date, and do not convert vague source language ("mid-2027")
   into a specific date.
4. "in_force": null means the source did not state it. Say "the source does not
   state whether this is in force" — never guess true or false.
5. If retrieval returned no records, say exactly: "I found no records on this in
   the AI Law Tracker dataset." Then stop. Do not answer from prior knowledge.
6. Note the freshness: records carry updated_at (last changed) and checked_at
   (last verified). If the user is making a decision, tell them to confirm
   against the official source URL.
7. End every answer with: "Informational only — not legal advice. Data by
   AI Law Tracker (CC BY 4.0)."

RETRIEVED RECORDS:
{{passages}}

Step 3 — make the citation verifiable, not decorative

A URL in a footnote is only a citation if it resolves to the thing being cited. Two endpoints turn a record id into something a reviewer can actually follow:

  • /laws/{id}/sources returns the record’s own official link plus the curated jurisdiction sources, each tiered primary or secondary. Basic tiers get primary sources only; Developer and above also see secondary/analysis links. Only real, held URLs are ever returned.
  • /laws/{id}/citations returns the identifier, the audited alternate names a law is known by, and a suggested citation string. It is free at every tier and it is explicitly labelled a formatting convenience — it is not an official citation, and you should not present it as one.
  • Alternate names matter for retrieval quality: users search for “the Colorado AI Act”, not “SB 24-205”. Feeding the audited aka list back into your query expansion catches those.
Terminal — the sources behind one record
curl -s "https://ai-law-tracker.com/api/v1/laws/5f9c2b0a-1e34-4d21-9a77-0c1b2d3e4f56/sources" \
  -H "X-API-Key: alt_free_your_key_here"
Terminal — the citation projection for one record
curl -s "https://ai-law-tracker.com/api/v1/laws/5f9c2b0a-1e34-4d21-9a77-0c1b2d3e4f56/citations" \
  -H "X-API-Key: alt_free_your_key_here"

Step 4 — evaluate the loop, not the vibes

Because every answer is supposed to be traceable, you can test that property mechanically instead of eyeballing outputs.

  • Citation coverage. Parse the generated answer and assert that every URL it cites appeared in the retrieved payload. Any URL that did not is, by definition, unsupported.
  • Empty-retrieval behaviour. Ask about something genuinely outside the dataset. The correct output is the refusal string, not a plausible paragraph.
  • Jurisdiction leakage. Ask a California question and assert that no cited record has a different jurisdiction.slug unless the answer explicitly frames it as out-of-state context.
  • Date fidelity. Assert that any date in the answer appears verbatim in a retrieved status, summary or raw_deadline field. This catches the most damaging class of error in this domain.
  • Freshness. Surface checked_at in your UI. A record verified last week and a record verified last quarter should not look identical to a reviewer.

Step 5 — attribution is not optional

Every record ships with its own attribution string, and the API meta block repeats it. The data is CC BY 4.0: you may redistribute it, including inside a product, provided you attribute. Propagating the field you were handed is the least error-prone way to comply.

If your agent surfaces obligations, penalties or risk scores from the interpreted endpoints, the same discipline applies — those responses carry both attribution and a disclaimer, and the disclaimer belongs in front of the user, not just in your logs.

Endpoints used in this guide

GET
/v1/searchText retrieval over title + summary. `q` required.
GET
/v1/lawsFiltered retrieval by scope / jurisdiction / status / in_force / updated_since.
GET
/v1/laws/{id}Re-fetch one record by id when expanding a citation.
GET
/v1/laws/{id}/sourcesTiered primary/secondary source URLs behind a record.
GET
/v1/laws/{id}/citationsIdentifier, audited alternate names, suggested citation. Free.
GET
/v1/jurisdictionsSlug vocabulary for constraining retrieval.
GET
/v1/openapi.jsonThe full OpenAPI 3.1 contract, for generated clients and tool schemas.

Full reference in the developer docs. Plans and limits on pricing.

FAQ

Does grounding on this dataset stop my agent from hallucinating?

No — and be sceptical of anyone who says it does. The retrieval path is deterministic and contains no model, so the evidence handed to your agent cannot be fabricated. The generation step remains a language model and can still mis-summarise. What grounding gives you is a checkable answer: every claim should carry an official_url a reviewer can open.

How should the agent behave when nothing is retrieved?

It should say so and stop. An empty result is real information about coverage. Falling back to pre-training knowledge is exactly the failure grounding exists to prevent, so make the refusal string an explicit, testable rule in the system prompt.

Should I embed the dataset in a vector store instead of calling the API?

You can — the data is CC BY 4.0. Just remember the trade-off: an embedded snapshot goes stale, and in this domain a stale status field is the whole problem. If you do snapshot, poll /v1/changes (Developer+) or subscribe a webhook (Pro+) to keep it current, and keep updated_at and checked_at in your index.

Which endpoint should the agent use for "what must we do about this"?

Not /v1/search. The derived duties live on /v1/obligations and POST /v1/assess, which are grounded in real law records plus their primary sources with no LLM in the derivation path. They are Pro-and-above; lower tiers get a one-item preview with an upgrade hint.

Keep going

🔌 Track AI laws inside Claude Code and Cursor with MCP🗺️ Fetch a state’s AI laws with the API📡 Catch effective-date changes with webhooks🗓️ Build an AI compliance deadline calendar📦 The dataset →🗓️ AI law deadlines →💳 Plans & limits →

Data by AI Law Tracker, licensed CC BY 4.0 — attribution required: “Data by AI Law Tracker (CC BY 4.0) — https://ai-law-tracker.com”. AI Law Tracker provides informational data, not legal advice. Verify every record against its official source before relying on it.