Efficient Polling with last_checked
When you're monitoring a set of cases for new activity, you don't need a full docket response on every check. The monitor endpoint returns a lightweight change signal — whether the case has updated since a given timestamp — so you can fetch the full docket only when something actually changed. At $0.99 per call either way, the value is in reduced data transfer, faster responses, and a cleaner separation between change detection and data retrieval.
The Monitor Endpoint
GET /v2/monitor takes the same court_code and case_id parameters as GET /v2/case, plus a required last_checked timestamp. It returns a change signal indicating whether the case has seen activity since that timestamp, without the full docket payload.
Use /v2/monitor when you need to know whether something changed. Use /v2/case when you need to know what changed. A typical monitoring workflow calls /v2/monitor on a schedule and calls /v2/case (or /v2/case with last_checked for a delta) only when the monitor response indicates activity.
Using last_checked
last_checked is an ISO-8601 timestamp with a timezone offset — for example, 2025-03-15T14:30:00-05:00 for Eastern time. It is required for the monitor endpoint.
The monitor endpoint compares last_checked against the case's most recent docket activity timestamp. If the case has changed since last_checked, the response signals a change. If not, it signals no change. Either way, you pay $0.99 — but you avoid pulling a full docket response for every check on every quiet case in your portfolio.
You can also pass last_checked to GET /v2/case and the batch endpoint (POST /v2/cases/batch). In those contexts it triggers a delta block in the response — a list of activity since that timestamp — alongside the full case context. Use the monitor endpoint when you want the change signal only; use last_checked on a case query when you want the delta alongside full context.
Implementation
The x402 payment flow for the monitor endpoint is identical to any other paid endpoint — probe, pay, retry.
import httpx, json
# build_payment_tx — see "How x402 Works" guide
def check_for_changes(court_code: str, case_id: str, last_checked: str) -> dict:
url = "https://api.docketlayer.ai/v2/monitor"
params = {
"court_code": court_code,
"case_id": case_id,
"last_checked": last_checked,
}
# Step 1: probe
probe = httpx.get(url, params=params)
assert probe.status_code == 402
requirements = json.loads(probe.headers["X-Payment-Requirements"])
# Step 2: build payment
encoded_payment = build_payment_tx(
client, payer,
requirements["payTo"],
int(requirements["maxAmountRequired"]) / 1e6,
)
# Step 3: retry with payment
response = httpx.get(url, params=params, headers={"x402-payment": encoded_payment})
response.raise_for_status()
return response.json()// buildPaymentTx — see "How x402 Works" guide
async function checkForChanges(
courtCode: string,
caseId: string,
lastChecked: string
): Promise<object> {
const url = new URL('https://api.docketlayer.ai/v2/monitor');
url.searchParams.set('court_code', courtCode);
url.searchParams.set('case_id', caseId);
url.searchParams.set('last_checked', lastChecked);
// Step 1: probe
const probe = await fetch(url.toString());
const requirements = JSON.parse(probe.headers.get('X-Payment-Requirements')!);
// Step 2: build payment
const encodedPayment = await buildPaymentTx(
connection, payer,
requirements.payTo,
parseInt(requirements.maxAmountRequired) / 1e6,
);
// Step 3: retry with payment
const response = await fetch(url.toString(), {
headers: { 'x402-payment': encodedPayment },
});
return response.json();
} Building a Polling Loop
A typical polling loop maintains a watch list of cases with per-case last_checked timestamps. On each iteration, it calls /v2/monitor for each case, fetches the full update for any that signal a change, and updates the stored timestamps.
import time
from datetime import datetime, timezone
# Cases to monitor: {"court_code:case_id": "last_checked_iso"}
watch_list = {
"nysd:1:24-cv-09822": "2025-01-01T00:00:00-05:00",
"cacd:2:23-cv-04501": "2025-01-01T08:00:00-08:00",
"ilnd:1:24-cv-01123": "2025-01-01T00:00:00-06:00",
}
POLL_INTERVAL_SECONDS = 3600 # check every hour
while True:
for key, last_checked in list(watch_list.items()):
court_code, case_id = key.split(":", 1)
result = check_for_changes(court_code, case_id, last_checked)
if result.get("changed"):
print(f"Case {case_id} in {court_code} has new activity — fetching full update")
# Fetch full docket with case_query when change detected
# ...
# Update last_checked to now
watch_list[key] = datetime.now(timezone.utc).isoformat()
time.sleep(POLL_INTERVAL_SECONDS) Update last_checked after every check, not just on changes
last_checked timestamp after each successful monitor call — not only when a change is detected. If you only update on change, a case with no activity will re-evaluate the same window on every poll, which is wasted cost. Advancing the timestamp on each check ensures the window always covers only the period since the last poll.
Polling Intervals
Courts update their dockets on their own schedules — federal courts typically publish filings within minutes of receipt, while some state portals batch updates overnight. Polling more frequently than the court publishes returns no new data but still costs $0.99 per call per case.
A reasonable default for federal courts is hourly. For state courts with batch publishing schedules, daily or twice-daily polling is sufficient. Check GET /v2/status for court-specific metadata if you need to calibrate intervals per jurisdiction.