Automating Docket Monitoring

Guides  ·  Use Cases

Automated docket monitoring is a scheduled job that queries a defined set of cases on a regular cadence, persists state between runs, and surfaces new activity for downstream processing. The key to keeping costs predictable is the last_checked parameter — each run fetches only the delta since the previous run, so quiet cases cost the same per check as active ones but return less data.

Overview

The job maintains a state file that maps each case to the timestamp of its last successful check. On each run, it reads the state, queries each case with that timestamp, processes any new activity, and writes the updated timestamps back to the state file. If a query fails transiently (portal down, coverage pending), the timestamp is not advanced — the next run covers the same window.

Persisting State

State persistence can be as simple as a JSON file on disk, or as robust as a database row per case. The only requirement is that last_checked survives between runs and is updated atomically after a successful query. A flat JSON file is sufficient for portfolios of a few hundred cases.

import json
from pathlib import Path

STATE_FILE = Path("/var/lib/docketlayer/state.json")

def load_state() -> dict:
    """Load per-case last_checked timestamps."""
    if STATE_FILE.exists():
        return json.loads(STATE_FILE.read_text())
    return {}

def save_state(state: dict) -> None:
    STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
    STATE_FILE.write_text(json.dumps(state, indent=2))

# State shape:
# {
#   "nysd:1:24-cv-09822": "2025-03-01T12:00:00+00:00",
#   "cacd:2:23-cv-04501": "2025-03-01T12:00:00+00:00",
# }

The Sweep Function

The sweep iterates over the case list, queries each one with the stored last_checked timestamp, collects updates, and advances the state. Transient errors — 422 for pending coverage, 503 for portal outages — are caught and skipped without updating the state.

import httpx, json
from datetime import datetime, timezone

# Cases to monitor — court_code:case_id keys
CASES = [
    "nysd:1:24-cv-09822",
    "cacd:2:23-cv-04501",
    "ilnd:1:24-cv-01123",
    "txsd:4:24-cv-00789",
]

DEFAULT_LAST_CHECKED = "2025-01-01T00:00:00+00:00"

def run_sweep():
    state = load_state()
    now = datetime.now(timezone.utc).isoformat()
    updates = []

    for key in CASES:
        court_code, case_id = key.split(":", 1)
        last_checked = state.get(key, DEFAULT_LAST_CHECKED)

        params = {
            "court_code": court_code,
            "case_id":    case_id,
            "last_checked": last_checked,
        }

        try:
            probe = httpx.get("https://api.docketlayer.ai/v2/case", params=params)
            requirements = json.loads(probe.headers["X-Payment-Requirements"])
            encoded = build_payment_tx(
                solana_client, payer,
                requirements["payTo"],
                int(requirements["maxAmountRequired"]) / 1e6,
            )
            resp = httpx.get(
                "https://api.docketlayer.ai/v2/case",
                params=params,
                headers={"x402-payment": encoded},
            )
            resp.raise_for_status()
            result = resp.json()

            # Process the result (store, notify, summarize, etc.)
            delta = result.get("delta", {})
            if delta.get("new_filings"):
                updates.append({"key": key, "result": result})

            # Advance last_checked
            state[key] = now

        except httpx.HTTPStatusError as e:
            if e.response.status_code in (422, 503):
                # Coverage pending or portal down — preserve last_checked
                continue
            raise

    save_state(state)
    return updates


if __name__ == "__main__":
    updates = run_sweep()
    print(f"Sweep complete. {len(updates)} case(s) with new activity.")

Batch Sweeps

For portfolios of more than a few cases, use the batch endpoint to reduce the number of x402 payment cycles. A single batch call can cover up to 50 cases, replacing 50 individual probe-pay-retry cycles with one.

import httpx, json
from datetime import datetime, timezone

def run_batch_sweep():
    state = load_state()
    now = datetime.now(timezone.utc).isoformat()

    queries = [
        {
            "court_code": key.split(":", 1)[0],
            "case_id":    key.split(":", 1)[1],
            "last_checked": state.get(key, DEFAULT_LAST_CHECKED),
        }
        for key in CASES
    ]

    probe = httpx.post(
        "https://api.docketlayer.ai/v2/cases/batch",
        json={"queries": queries},
    )
    requirements = json.loads(probe.headers["X-Payment-Requirements"])
    encoded = build_payment_tx(
        solana_client, payer,
        requirements["payTo"],
        int(requirements["maxAmountRequired"]) / 1e6,
    )
    resp = httpx.post(
        "https://api.docketlayer.ai/v2/cases/batch",
        json={"queries": queries},
        headers={"x402-payment": encoded},
    )
    resp.raise_for_status()
    results = resp.json()

    updates = []
    for key, result in zip(CASES, results):
        if result.get("error"):
            continue  # preserve last_checked for errored cases
        state[key] = now
        if result.get("delta", {}).get("new_filings"):
            updates.append({"key": key, "result": result})

    save_state(state)
    return updates

Handle batch errors per result

The batch response returns an array of results — one per query, in the same order as the request. Check each result individually for errors before advancing its last_checked timestamp. Do not advance the timestamp for any case that returned an error response.

Production Considerations

Wallet balance. A portfolio of 100 cases checked hourly costs $2.40 per day. Keep enough USDC in the signing wallet to cover several days of sweeps without manual top-up. Implement a balance check at job startup and alert if the balance falls below a threshold.

Blockhash expiry. Solana transaction signatures are valid for roughly five minutes. Build the payment transaction immediately before the retry request — do not cache a signed transaction across job runs or across multiple case queries within the same sweep. Generate a fresh transaction per API call.

State file integrity. If the process is interrupted mid-sweep, some timestamps will be advanced and others won't. This is acceptable — cases with un-advanced timestamps will simply re-query the same window on the next run. The only risk is duplicate processing of the same delta. Design your downstream logic to be idempotent with respect to filing records.