Calendar · iCal · cron

Build an AI compliance deadline calendar

Effective dates are the part of AI regulation that actually lands in someone’s quarter. GET /v1/deadlines returns them as JSON or as a subscribable .ics — free at every tier — with the verbatim source text alongside every parsed date so you can audit the extraction instead of trusting it.

Published · AI Law Tracker Editorial Team

Get a free keyAPI docs

The two-minute version: subscribe to the .ics

If all you want is the dates in your calendar app, you do not need to write any code. The same endpoint serves an RFC 5545 feed, and calendar clients will re-poll it on their own schedule.

Calendar subscription URLs
# Everything, upcoming only (the default)
https://ai-law-tracker.com/api/v1/deadlines?format=ical

# One jurisdiction
https://ai-law-tracker.com/api/v1/deadlines?format=ical&jurisdiction=california

# One scope (state | federal | eu | global)
https://ai-law-tracker.com/api/v1/deadlines?format=ical&scope=eu

# Most clients also accept the webcal:// scheme for "subscribe", not "import"
webcal://ai-law-tracker.com/api/v1/deadlines?format=ical
format=ics and format=icalendar are accepted aliases of format=ical. The response is text/calendar with the filename ai-law-deadlines.ics; each entry is an all-day event carrying the verbatim source deadline text and the primary-source URL.

Step 1 — the JSON feed

The default shape is JSON. Parameters: scope, jurisdiction (exact slug), upcoming (default true), all (an alias for upcoming=false), and format. No key is required, and no tier is gated off this endpoint.

Terminal
curl -s -G "https://ai-law-tracker.com/api/v1/deadlines" \
  -H "X-API-Key: alt_free_your_key_here" \
  --data-urlencode "scope=state" \
  --data-urlencode "upcoming=true"
Example response (values illustrative, shape live)
{
  "api_version": "v1",
  "data": [
    {
      "scope": "eu",
      "jurisdiction": "European Union",
      "jurisdiction_slug": "eu",
      "laws": ["EU AI Act (Regulation 2024/1689)"],
      "status": "In force — phased application",
      "raw_deadline": "High-risk obligations apply from August 2, 2026",
      "date": "2026-08-02",
      "date_precision": "day",
      "is_upcoming": true,
      "dates": [{ "iso": "2026-08-02", "precision": "day", "sort": "2026-08-02" }],
      "source_url": "https://eur-lex.europa.eu/eli/reg/2024/1689/oj",
      "source_tier": "primary"
    }
  ],
  "meta": {
    "count": 1,
    "undated": 9,
    "upcoming_only": true,
    "generated_at": "2026-08-06T00:00:00.000Z",
    "ical": "https://ai-law-tracker.com/api/v1/deadlines?format=ical",
    "upgrade": "These are the public effective dates. For the derived \"what must I do about each deadline\"…",
    "attribution": "Data by AI Law Tracker (CC BY 4.0) — https://ai-law-tracker.com",
    "disclaimer": "Informational only — not legal advice…"
  }
}

Step 2 — why raw_deadline, date and date_precision are three different things

This is the part of the endpoint worth understanding before you build anything on it, because it is where a compliance calendar usually goes wrong.

Source deadline text is prose. Real examples read like “August 2, 2026”, “In effect since July 31, 2025”, or “Layered; AI content-labeling effective Sept 1, 2025”. A naive Date.parse() over that prose will happily return *a* date — frequently the wrong one, and always without telling you how much of the string it understood.

So the feed does three separate things instead of one lossy thing:

  • `raw_deadline` is the curated source text, verbatim, never reworded. It is what you show a human and what you audit an extraction against.
  • `date` is a normalised ISO date pulled out with strict, explicit patterns — ISO 8601, “Month DD, YYYY”, “DD Month YYYY”, “Month YYYY”, or a bare year. Impossible dates (a February 30th typo) are rejected rather than normalised into something real-looking. When the text names several dates, dates[] carries all of them and date is the earliest upcoming one.
  • `date_precision` tells you how much of that date was actually stated: day, month, or year. A month precision entry means the source said “July 2027” — the 2027-07-01 in date is a sort key, not a claim that something is due on the 1st.
  • `undated` in meta counts jurisdictions whose deadline text contains no explicit date at all. They are counted, never given an invented date, and never appear in data.
Practical rule: sort and alert on date, but never *display* a date whose date_precision is not day without qualifying it. Rendering “2027-07-01” for a source that said “July 2027” is how a team ends up planning against a deadline nobody set.

Step 3 — a cron that warns you before a date lands

A daily job is plenty: these dates move on a legislative timescale, not a market one. The example below alerts at fixed horizons and respects date_precision in what it says.

crontab — daily at 08:00
0 8 * * * /usr/bin/python3 /opt/alt/deadline_alerts.py >> /var/log/alt-deadlines.log 2>&1
Python — deadline_alerts.py
import datetime as dt
import os

import requests

BASE = "https://ai-law-tracker.com/api/v1"
HORIZONS = {90: "90 days", 30: "30 days", 7: "1 week"}
PRECISION_NOTE = {
    "day": "",
    "month": " (source states the month only — day is not specified)",
    "year": " (source states the year only — month and day are not specified)",
}


def upcoming_deadlines(scope=None, jurisdiction=None):
    params = {"upcoming": "true"}
    if scope:
        params["scope"] = scope
    if jurisdiction:
        params["jurisdiction"] = jurisdiction

    r = requests.get(
        f"{BASE}/deadlines",
        params=params,
        headers={"X-API-Key": os.environ["ALT_API_KEY"]},
        timeout=30,
    )
    r.raise_for_status()
    return r.json()


def main():
    payload = upcoming_deadlines(scope="state")
    today = dt.date.today()

    for entry in payload["data"]:
        due = dt.date.fromisoformat(entry["date"])
        days_out = (due - today).days
        if days_out not in HORIZONS:
            continue

        note = PRECISION_NOTE[entry["date_precision"]]
        alert(
            f"{entry['jurisdiction']} — {HORIZONS[days_out]} out ({entry['date']}){note}\n"
            # ALWAYS carry the verbatim source text and the source link.
            f"Source text: \"{entry['raw_deadline']}\"\n"
            f"Primary source ({entry['source_tier']}): {entry['source_url']}\n"
            f"Laws: {', '.join(entry['laws']) or 'n/a'}"
        )

    if payload["meta"]["undated"]:
        alert(
            f"FYI: {payload['meta']['undated']} tracked jurisdictions have deadline text "
            "with no explicit date. They are not on the calendar — review them by hand."
        )


if __name__ == "__main__":
    main()
Note the last block. The honest failure mode of a deadline calendar is silence about what it could not parse — surfacing the undated count turns an invisible gap into a review task.

Step 4 — looking backwards

The feed is forward-looking by default. For an audit trail — “which obligations were already in force on this date” — include past effective dates:

  • Each entry keeps is_upcoming so you can split the two sets client-side from one call.
  • source_tier tells you what backs the entry: primary (an official government source), secondary, or unverified. Weight your review accordingly.
  • The human-readable view of the same data lives at /ai-law-deadlines.
Terminal
# both spellings do the same thing
curl -s "https://ai-law-tracker.com/api/v1/deadlines?upcoming=false"
curl -s "https://ai-law-tracker.com/api/v1/deadlines?all=true"

What this endpoint does not answer

A deadline calendar tells you *when*. It does not tell you *what you must do* — that is the interpreted layer, and it lives on separate endpoints: GET /v1/obligations (duties for a jurisdiction and/or sector) and POST /v1/assess (a business profile in, applicable obligations plus a reproducible 1–10 risk score out). Both are Pro-and-above; lower tiers receive a one-item preview with an upgrade hint rather than a blank wall.

Endpoints used in this guide

GET
/v1/deadlinesThe calendar. Params: scope, jurisdiction, upcoming, all, format=json|ical|ics. Free at every tier.
GET
/v1/jurisdictionsValid jurisdiction slugs for the filter.
GET
/v1/obligationsThe derived "what must I do" layer. Pro+ for the full list.
POST
/v1/assessProfile → applicable obligations + risk score. Pro+.

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

FAQ

Is the deadline feed free?

Yes — free at every tier, including anonymous. These are public primary-source effective dates. The paid layer is the interpretation of them (obligations and risk), not the dates themselves.

Why is a jurisdiction I track missing from the calendar?

Most likely its deadline text carries no explicit calendar date. Those are counted in meta.undated and deliberately excluded from data rather than given a fabricated date. Fetch with upcoming=false as well, in case its date has already passed.

What does date_precision: "month" mean for my alerting?

That the source named a month but not a day. The ISO date pins the 1st purely so entries can be sorted and rendered — treat it as "sometime in that month" and say so in whatever you show a human.

How often should I refresh?

Daily is more than enough. The calendar is derived deterministically from the curated corpus and meta.generated_at tells you when the snapshot you hold was built.

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 Ground an AI agent in real AI-law data📦 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.