Skip to main content
GET
List ERA service lines

Authorizations

Authorization
string
header
required

Marketplace API key

Headers

X-Organization-Id
string
required

Target clinic organization ID

Query Parameters

dateFrom
string

Filter from service date (ISO 8601). A bare YYYY-MM-DD starts at 00:00:00 UTC. EXCLUDES ROWS WITH NO SERVICE DATE, and a remit line CAN have none. Most lines — anchored or not — do carry one, so a date window is NOT a way to isolate unanchored money (use matched=false for that); it is simply not exhaustive. Sweep with no date filter, or on updatedSince, when you need every row.

Example:

"2025-01-01"

dateTo
string

Filter to service date (ISO 8601). A bare YYYY-MM-DD is inclusive to the END of that day in UTC, so a caller in a negative-offset timezone gets a window that closes earlier in local time. Excludes rows with no service date, same as dateFrom — see that field.

Example:

"2025-12-31"

billId
string

Filter to remit lines matched to this bill. Unmatched remit lines (billId: null) can never satisfy this filter, so a bill-scoped pull will not see payer dollars the EHR failed to tie to a claim.

claimId
string

Filter to remit lines matched to this claim.

code
string

Filter by procedure code (CPT/HCPCS), comma-separated. Exact match, case-insensitive on both the value you send and the code as stored. Supplying the parameter with NO values — ?code= or ?code=, — is a 400, not an empty filter: build the query string so an empty cohort OMITS the parameter, because silently dropping it would return every remit line in the organization for a request that asked for a few.

Example:

"J3245,J0717"

updatedSince
string

Only return service lines whose record was created or modified at/after this timestamp (ISO 8601). Payer money is a moving target — recoupments, secondary payers and 835 PLB adjustments re-touch a line for 90+ days after service — so this is the cursor to sync on. Results are ALWAYS ordered by (updatedAt, id) ascending, cursor or not — a first backfill with no cursor gets the same order. THIS CURSOR CANNOT REPORT DELETIONS. ModMed's unposted -> posted transition hard-deletes a remit's composite-keyed lines and re-inserts them under new ids (see the id field). The delete bumps no timestamp and leaves no tombstone, so a delta-only consumer accumulates both copies and double-counts payer dollars. Pair this cursor with a periodic full re-pull of the claims/bills you track. UNMATCHED LINES HAVE NO ANCHOR: when claimId is null there is no claim or bill to reconcile against, and ?billId=/?claimId= cannot return those rows at all. Only a periodic pull with NO billId, claimId, dateFrom or dateTo filter can detect that one was deleted — serviceDate can be absent, so a date window is not exhaustive either. Scope the sweep with matched=false instead. RECOMMENDED RECIPE: request page=1 every time and advance updatedSince to the LAST row's updatedAt after each page, rather than walking page=2,3,… through one long run. Deep paging is not stable here: a row re-touched by an ERA re-sync while you page moves to the end of the ordering, every later row shifts down one, and the row that crosses the page boundary is skipped — its updatedAt stays below the cursor you end up storing, so it is never delivered again. Rows sharing the boundary updatedAt re-deliver on the next pull, since the bound is inclusive — dedupe on id. One caveat to the recipe: ERA sync re-touches a whole claim at once, so if MORE rows share a single EMITTED updatedAt than fit in one page, advancing the cursor cannot make progress — page deeper (page=2,3,…) within that timestamp until it is exhausted, then advance. The threshold is a shared MILLISECOND, not a shared instant: the column is a microsecond timestamp and this field truncates, so rows stored at .999750 and .999999 are distinct in the database but are both emitted — and both matched by a cursor — as .999Z. OVERLAP YOUR CURSOR. updatedAt is stamped with the WRITING TRANSACTION'S START time, and rows only become visible at its COMMIT. A sync that starts at 10:00 and commits at 10:05 writes rows stamped 10:00 that a poll at 10:02 cannot see — if that poll advanced your cursor to 10:01, those rows are below it forever. Re-request from (your stored cursor MINUS a margin at least as long as the longest sync transaction; minutes, not seconds) and dedupe on id. Storing the cursor with no overlap will silently lose whole ERAs.

Example:

"2025-06-01T00:00:00.000Z"

updatedUntil
string

Only return service lines whose record was last modified at/before this timestamp (ISO 8601). A bare YYYY-MM-DD is treated as the END of that day IN UTC, so a window like updatedSince=2026-03-01&updatedUntil=2026-03-03 includes everything re-touched on the 3rd. PREFER THE BARE DATE for calendar windows. An explicit instant is compared inclusively at MILLISECOND precision, and the column stores microseconds — so …T23:59:59.999Z silently excludes rows at .999001-.999999, which your next window then starts above. If you must pass an instant, pass the next window's start (e.g. 2025-07-01T00:00:00.000Z), which over-delivers by one instant rather than losing rows.

Example:

"2025-06-30"

matched
boolean

Anchor filter. false returns ONLY the lines this API reports with claimId: null — payer dollars with no claim of ours behind them — which is otherwise findable only by sweeping the organization's entire remit history: billId=/claimId= can never return them, and a dateFrom/dateTo window is not exhaustive, because serviceDate can be absent. true returns only anchored lines. Omit for both. KEYED ON THE CLAIM, NOT THE BILL. billId is resolved THROUGH the claim, so a line with no claim never has a bill — but a line whose claim carries no unarchived bill is still matched=true with billId: null. If you are looking for money you cannot post, filter matched=false AND check billId on the rest. It follows the RESPONSE, not the EHR: a line whose matched claim was later archived reports claimId: null and is returned by matched=false, because that is the row whose linkage you cannot follow. It is not a cursor. Matched-ness changes without re-touching a line's updatedAt, so a line can move between the two cohorts and never reappear in an updatedSince walk (see claimId) — re-run the matched=false sweep on a schedule instead of subscribing to it.

Example:

false

Response

Success

code
string
required

Response code

Example:

"success"

data
object
required