Batch Querying

Guides  ·  Optimization

The batch endpoint lets you query up to 50 cases in a single request. Instead of sending 50 sequential x402 probe-pay-retry cycles, you send one. The trade-off is that all queries in the batch must use the same payment transaction — the payment requirement on the 402 response covers the full batch.

When to Use Batch

Batch querying is the right approach when you're sweeping a portfolio of known cases on a regular schedule — a monitoring service that checks dozens of active cases nightly, a dashboard that surfaces the latest status for a docket list, or an initial seeding operation for a new set of cases.

It's less appropriate for on-demand lookups where you need results for a single case immediately, or for workloads where cases are distributed across long time horizons. For those, direct GET /v2/case calls give you better flow control.

Request Format

The batch endpoint accepts a JSON body with a single queries array. Each item in the array takes the same parameters as a direct case query:

  • case_id — required
  • court_code — required
  • last_checked — optional ISO-8601 timestamp; triggers a delta response for that case
  • context — optional, basic or full
  • language — optional, en or fr
  • filing_types — optional comma-separated filter

You can mix and match parameters across queries in the same batch — some with last_checked, some without, different context depths, different courts.

Implementation

The x402 flow is the same probe-pay-retry pattern as a single query. The batch endpoint returns a 402 on the probe with payment requirements covering the full batch. Build the payment transaction, then retry with the x402-payment header.

import httpx, json

# build_payment_tx — see "How x402 Works" guide

queries = [
    {"case_id": "1:24-cv-09822", "court_code": "nysd"},
    {"case_id": "1:23-cv-11195", "court_code": "nysd", "last_checked": "2025-01-01T00:00:00-05:00"},
    {"case_id": "2:24-cv-00123", "court_code": "cacd"},
]

url = "https://api.docketlayer.ai/v2/cases/batch"

# Step 1: probe — no payment header
probe = httpx.post(url, json={"queries": queries})
assert probe.status_code == 402
requirements = json.loads(probe.headers["X-Payment-Requirements"])

# Step 2: build payment for the full batch
encoded_payment = build_payment_tx(
    client, payer,
    requirements["payTo"],
    int(requirements["maxAmountRequired"]) / 1e6,
)

# Step 3: retry with payment
response = httpx.post(
    url,
    json={"queries": queries},
    headers={"x402-payment": encoded_payment},
)
response.raise_for_status()
results = response.json()
// buildPaymentTx — see "How x402 Works" guide

const queries = [
  { case_id: '1:24-cv-09822', court_code: 'nysd' },
  { case_id: '1:23-cv-11195', court_code: 'nysd', last_checked: '2025-01-01T00:00:00-05:00' },
  { case_id: '2:24-cv-00123', court_code: 'cacd' },
];

const url = 'https://api.docketlayer.ai/v2/cases/batch';

// Step 1: probe
const probe = await fetch(url, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ queries }),
});
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, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x402-payment': encodedPayment,
  },
  body: JSON.stringify({ queries }),
});

const results = await response.json();

Incremental Updates

Use last_checked per case to pull only what changed since your last sweep. This reduces response size significantly for cases that see infrequent activity and makes it easier to build alerting logic — if the delta block is empty, nothing happened. For a monitoring loop built around this pattern, see Efficient Polling with last_checked.

from datetime import datetime, timezone

# Store last_checked per case after each sweep
last_checked = {
    "nysd:1:24-cv-09822": "2025-01-15T00:00:00-05:00",
    "nysd:1:23-cv-11195": "2025-01-15T00:00:00-05:00",
    "cacd:2:24-cv-00123": "2025-01-15T08:00:00-08:00",
}

# Build queries using stored timestamps
queries = [
    {
        "case_id": case_id.split(":", 1)[1],
        "court_code": case_id.split(":", 1)[0],
        "last_checked": ts,
    }
    for case_id, ts in last_checked.items()
]

# After a successful batch, update stored timestamps
now = datetime.now(timezone.utc).isoformat()
for key in last_checked:
    last_checked[key] = now

Store last_checked per case, not per batch

Each case in a portfolio may update at a different rate. Storing a single timestamp for the whole batch means you'll miss updates on cases that changed between batch runs if the timestamp is too coarse. Track timestamps per case and update them individually after a successful result.

Limits and Billing

A single batch request can contain between 1 and 50 queries. Batches larger than 50 must be split across multiple requests.

Billing is per successful result. If a query in the batch returns an error — because the court is unreachable, the case ID is invalid, or coverage is pending — that query is not charged. You pay only for cases that return data.

422 responses for courts with coverage='planned' (coverage details) are not billed. 503 responses for temporarily unreachable portals are not billed. Check GET /v2/status to confirm court coverage before including a court in a batch sweep.