Data API · version 1 · for Pro and Business
The SheriffSaleData data API is read-only JSON over the same sheriff sales the site shows, from the sale lists of sheriffs, clerks of court and master commissioners alike: search them, fetch one with its case, parties, figures and every postponement, and keep a copy of your markets in step. It comes with Pro and Business, uses keys you create on your account page, and shares your plan’s monthly records with CSV export; through the API a sale counts once a month, however often you fetch it.
Every plan starts with a 3-day free trial, and the API opens with the first payment. To use it the day you sign up, start your plan from your account page and the trial ends early.
SHERIFFSALEDATA_API_KEY, and send it in the Authorization header:curl https://www.sheriffsaledata.com/api/v1/me \
-H "Authorization: Bearer $SHERIFFSALEDATA_API_KEY"curl "https://www.sheriffsaledata.com/api/v1/sales?state=NJ&status=upcoming&limit=100" \
-H "Authorization: Bearer $SHERIFFSALEDATA_API_KEY"Authorization: Bearer <key>, and nothing else is accepted: not your website sign-in (the API ignores cookies), not an X-API-Key header, not a query parameter.ssd_api_ followed by 46 letters and digits, matching ^ssd_api_[0-9A-Za-z]{46}$: give that pattern to your secret scanner. Its last six characters are a checksum, so a mistyped or cut-short key is refused at once with 401 invalid_key.X-Request-Id, which is an error’s request_id too. It is all we need to look into a request: never send us a key.Every endpoint is under https://www.sheriffsaledata.com/api/v1: the www address, over HTTPS, with no trailing slash (an address ending in a slash is answered with a redirect to the one without).
YYYY-MM-DD, times ISO 8601 in UTC, money a number of US dollars, and a value the sale list does not state is null, never left out.format parameter. CSV export is part of the website, from a search, signed in; it shares the same monthly allowance and does not take API keys (a request to it carrying an Authorization header is refused)./api/v2, announced by email at least 30 days ahead.https://www.sheriffsaledata.com/api/v1 need no key.| Limit | Starter | Pro | Business |
|---|---|---|---|
| Price | $29 a month | $49 a month | $99 a month |
| The data API | — | Included | Included |
| Records a monthBy CSV export and the API together. Through the API, a sale counts once a month. | — | 3,000 | 10,000 |
| New records through the APIIn any 24 hours | — | 1,000 | 2,500 |
| Requests a minuteEvery key together | — | 60 | 120 |
| Requests a dayUTC, from midnight | — | 5,000 | 10,000 |
| Requests at once | — | 2 | 3 |
| Live keys | — | 2 | 5 |
| Sales per page100 unless asked | — | Up to 200 | Up to 200 |
The API opens with the first payment, as CSV export does: during the 3-day free trial no key can be created, and a key’s requests are answered 403 trial_limit. To use it today, end the trial early: Start Pro now (or Business), under Subscription on your account page, takes the first payment at checkout, opens CSV export and the API as soon as it goes through, and cancels the trial once the payment is confirmed, so you are never charged twice. Your saved searches and alerts carry on.
A new plan’s limits apply from the next request. If a payment fails, the API pauses (403 payment_past_due) until it goes through, and your keys are kept. On a plan without the API, your keys stay listed on your account page, answered 403 forbidden_plan, until you revoke them or move back.
/me, /states, /states/{state}/counties, /sales/count, /api/v1 and openapi.json, every error, and any sale already fetched this month. Requests are never charged, only rate-limited.truncated: true and a next_cursor that resumes at the first one held back. Only when not one sale on a page can be served is the answer 429 quota_exceeded, whose details.limit says which allowance (month or day) and whose Retry-After says when it comes back: 00:00 UTC on the 1st, or when the oldest new record leaves the 24 hours.details.window (minute, day or in_flight). Wait Retry-After seconds and repeat the request.Retry-After.failed_auth) instead of 401. A working key from the same address is never refused for its neighbours’ mistakes: offices and automation platforms share addresses./me and the reason.Every response to a key that works carries the RateLimit headers, errors included; sales and /me add the allowance’s. The same numbers are in the body’s usage, for tools that hide headers. Answers given before the key is read carry only X-Request-Id: a 401, a key sent in the address, an unknown path or method, too many failed keys from one address, and a 503 when the API is paused or full.
| Header | What it says |
|---|---|
X-Request-Id | This response's id; quote it to support. The same as error.request_id. |
RateLimit-Limit | Requests allowed in the sliding minute, for the account across all its keys. The RateLimit headers come on every response to a key that works, errors included, and on none sent before the key is read: a 401, a key_in_url 400, an unknown path or method, a failed_auth 429, and a 503 when the API is paused or full. |
RateLimit-Remaining | Requests left in the sliding minute. |
RateLimit-Reset | Seconds until the oldest request in the minute leaves it (0 when none is counted). |
RateLimit-Policy | Every request window: requests a minute, then a UTC day (60;w=60, 5000;w=86400). |
X-Quota-Month-Limit | Records a month, by CSV export and the API together. |
X-Quota-Month-Used | Records used this month: CSV rows plus distinct sales through the API. |
X-Quota-Month-Remaining | Records left this month. |
X-Quota-Month-Reset | When the month starts again: 00:00 UTC on the 1st. |
X-Quota-Day-Limit | New records the API may count in any 24 hours. |
X-Quota-Day-Remaining | New records left in the last 24 hours. |
X-Records-Counted | Sales this response counted: those not already fetched this month. |
Retry-After | On a 429 or 503: seconds to wait before repeating the request. |
Every endpoint answers GET and nothing else (405, HEAD included, so nothing is spent on a body nobody reads), and an address under /api/v1 that is not one of these is a JSON 404. Parameters are strict: one the endpoint does not take is 400 unknown_parameter rather than ignored, because a misspelt filter that is quietly dropped returns a different list from the one asked for.
Search the sales your plan opens, a page at a time. Metered: each sale counts once a month. Upcoming sales, soonest first, unless you ask otherwise; total comes on the first page only.
| Parameter | Accepts | What it does |
|---|---|---|
q | Text, 1 to 100 characters | Text to find in a sale's address, town, ZIP, parcel number, case number or sheriff's number. Never people: defendants and plaintiffs are not searched, and neither are legal descriptions. |
state | A two-letter code | Two-letter state code, as /states lists it. |
county | Text, 1 to 80 characters | A county, spelled exactly as /states/{state}/counties returns it. Needs state. |
type | One of mortgage_foreclosure, tax_foreclosure, hoa_foreclosure, judgment_execution, foreclosure | What is being foreclosed: a mortgage, unpaid tax, an association's lien, a money judgment, or unstated. |
plaintiff | Text, 1 to 120 characters | A published plaintiff's slug, as in /plaintiffs/{slug}. Organizations only. |
status | One of upcoming, past, all, rescheduled, sold; upcoming when left out | upcoming (from today), past, all, rescheduled (postponed or moved at least once) or sold (a held sale the sheriff reported sold). |
sale_date_from | A date, YYYY-MM-DD | Sales on or after this date (YYYY-MM-DD). The search page calls it from. |
sale_date_to | A date, YYYY-MM-DD | Sales on or before this date (YYYY-MM-DD). The search page calls it to. |
first_seen_after | An ISO 8601 date and time with its offset, or a date | Sales we first saw after this moment (ISO 8601 with an offset, or a date). For incremental sync, prefer sort=first_seen and its cursor. |
min_opening_bid | A number of US dollars, 0 or more | Opening bid at least this many US dollars. The search page calls it minBid. |
max_opening_bid | A number of US dollars, 0 or more | Opening bid at most this many US dollars. The search page calls it maxBid. |
sort | One of sale_date, -sale_date, first_seen, -first_seen, opening_bid, -opening_bid | sale_date (default; -sale_date for past), first_seen or -first_seen (when we first saw it: the order for keeping a copy in sync), opening_bid or -opening_bid. Nulls sort last. |
limit | A whole number, 1 to 200; 100 when left out | Sales per page, 1 to 200. |
cursor | As it came, up to 512 characters | The next_cursor of the previous page, as it came. Valid only with the same filters and sort. |
curl "https://www.sheriffsaledata.com/api/v1/sales?state=NJ&status=upcoming&limit=100" \
-H "Authorization: Bearer $SHERIFFSALEDATA_API_KEY"{
"object": "list",
"data": [
{
"id": "0000000000c0ffee",
"sale_type": "mortgage_foreclosure",
"state": "NJ",
"county": "Example",
"address": "123 Example St",
"city": "Exampleton",
"zip": "07000",
"parcel_id": "00-0000-000",
"sale_date": "2026-11-04",
"sale_time": "2:00 PM",
"sale_timezone": "America/New_York",
"sale_status": "scheduled",
"status_detail": "Adjourned",
"postponements": 2,
"rescheduled_from": null,
"status_history": [
"2026-10-01: Adjourned",
"2026-09-03: Scheduled"
],
"opening_bid": 185000,
"judgment_amount": 212450.17,
"appraised_value": null,
"deposit_required": null,
"sold_amount": null,
"sold_to": null,
"case_number": "F-000000-26",
"sheriff_number": "S-000000",
"plaintiff": "Example Mortgage Servicing LLC",
"defendant": "Jane Example",
"plaintiff_attorney": "Example Law Group",
"sale_subtype": null,
"notes": null,
"legal_description": null,
"sale_venue": null,
"sale_format": "In person",
"first_seen_at": "2026-10-11T10:05:12.000Z",
"last_seen_at": "2026-10-11T10:05:12.000Z",
"url": "https://www.sheriffsaledata.com/listings/0000000000c0ffee",
"other_ids": [
"00000000feedface"
]
}
],
"has_more": true,
"next_cursor": "eyJ2IjoxLCJzIjoic2FsZV9kYXRlIn0.ZXhhbXBsZS1jdXJzb3ItdGFn",
"truncated": false,
"total": 3344,
"filters": {
"state": "NJ",
"status": "upcoming",
"sort": "sale_date"
},
"usage": {
"records_counted": 1,
"month": {
"limit": 3000,
"csv": 120,
"api": 721,
"used": 841,
"remaining": 2159,
"resets_at": "2026-11-01T00:00:00Z"
},
"day": {
"limit": 1000,
"used": 211,
"remaining": 789,
"frees_at": "2026-10-13T09:12:44.512Z"
}
},
"data_as_of": "2026-10-12"
}How many sales match, for free. It takes the list’s filters except q, and no page: a count narrowed to one address or parcel would say whether that sale exists without fetching it.
| Parameter | Accepts | What it does |
|---|---|---|
state | A two-letter code | Two-letter state code, as /states lists it. |
county | Text, 1 to 80 characters | A county, spelled exactly as /states/{state}/counties returns it. Needs state. |
type | One of mortgage_foreclosure, tax_foreclosure, hoa_foreclosure, judgment_execution, foreclosure | What is being foreclosed: a mortgage, unpaid tax, an association's lien, a money judgment, or unstated. |
plaintiff | Text, 1 to 120 characters | A published plaintiff's slug, as in /plaintiffs/{slug}. Organizations only. |
status | One of upcoming, past, all, rescheduled, sold; upcoming when left out | upcoming (from today), past, all, rescheduled (postponed or moved at least once) or sold (a held sale the sheriff reported sold). |
sale_date_from | A date, YYYY-MM-DD | Sales on or after this date (YYYY-MM-DD). The search page calls it from. |
sale_date_to | A date, YYYY-MM-DD | Sales on or before this date (YYYY-MM-DD). The search page calls it to. |
first_seen_after | An ISO 8601 date and time with its offset, or a date | Sales we first saw after this moment (ISO 8601 with an offset, or a date). For incremental sync, prefer sort=first_seen and its cursor. |
min_opening_bid | A number of US dollars, 0 or more | Opening bid at least this many US dollars. The search page calls it minBid. |
max_opening_bid | A number of US dollars, 0 or more | Opening bid at most this many US dollars. The search page calls it maxBid. |
{
"object": "count",
"total": 212,
"filters": {
"state": "NJ",
"county": "Bergen",
"status": "upcoming"
},
"data_as_of": "2026-10-12"
}One sale, by any id it has had, with its status history. A sale’s id is the listing currently representing it, sixteen hexadecimal characters, and it changes when a better listing takes the sale over; an id the sale no longer carries is answered with the sale as it is now, id_changed: true, and the id you asked for as requested_id. An id that belongs to no listed sale, because the sale was stayed, cancelled or paid off or because we never issued it, is 404: there is no 410 to tell the two apart. Metered.
{
"object": "sale",
"data": {
"id": "0000000000c0ffee",
"sale_type": "mortgage_foreclosure",
"state": "NJ",
"county": "Example",
"address": "123 Example St",
"city": "Exampleton",
"zip": "07000",
"parcel_id": "00-0000-000",
"sale_date": "2026-11-04",
"sale_time": "2:00 PM",
"sale_timezone": "America/New_York",
"sale_status": "scheduled",
"status_detail": "Adjourned",
"postponements": 2,
"rescheduled_from": null,
"status_history": [
"2026-10-01: Adjourned",
"2026-09-03: Scheduled"
],
"opening_bid": 185000,
"judgment_amount": 212450.17,
"appraised_value": null,
"deposit_required": null,
"sold_amount": null,
"sold_to": null,
"case_number": "F-000000-26",
"sheriff_number": "S-000000",
"plaintiff": "Example Mortgage Servicing LLC",
"defendant": "Jane Example",
"plaintiff_attorney": "Example Law Group",
"sale_subtype": null,
"notes": null,
"legal_description": null,
"sale_venue": null,
"sale_format": "In person",
"first_seen_at": "2026-10-11T10:05:12.000Z",
"last_seen_at": "2026-10-11T10:05:12.000Z",
"url": "https://www.sheriffsaledata.com/listings/0000000000c0ffee",
"other_ids": [
"00000000feedface"
]
},
"requested_id": "00000000feedface",
"id_changed": true,
"usage": {
"records_counted": 0,
"month": {
"limit": 3000,
"csv": 120,
"api": 721,
"used": 841,
"remaining": 2159,
"resets_at": "2026-11-01T00:00:00Z"
},
"day": {
"limit": 1000,
"used": 211,
"remaining": 789,
"frees_at": "2026-10-13T09:12:44.512Z"
}
},
"data_as_of": "2026-10-12"
}Every state we cover, busiest first, with its counts. Free.
| Field | Type | What it holds |
|---|---|---|
state | string | Two-letter state code. |
name | string | The state's name. |
upcoming_sales | integer | Sales from today on. |
next_30_days | integer | Sales in the next 30 days. |
held_last_90_days | integer | Sales held in the last 90 days. |
next_sale_date | string (date), or null | The next sale's date. |
counties | integer | Counties covered. |
url | string (uri) | The state's page on the site. |
{
"object": "list",
"data": [
{
"state": "NJ",
"name": "New Jersey",
"upcoming_sales": 3344,
"next_30_days": 1210,
"held_last_90_days": 902,
"next_sale_date": "2026-10-13",
"counties": 41,
"url": "https://www.sheriffsaledata.com/sheriff-sales/new-jersey"
}
],
"data_as_of": "2026-10-12"
}A state’s counties, with the exact spelling the county filter wants. Free. A state we do not cover is a 404.
| Field | Type | What it holds |
|---|---|---|
county | string | The county, spelled exactly as the county filter wants it. |
state | string | Two-letter state code. |
upcoming_sales | integer | Sales from today on. |
next_30_days | integer | Sales in the next 30 days. |
held_last_90_days | integer | Sales held in the last 90 days. |
next_sale_date | string (date), or null | The next sale's date. |
url | string (uri) | The county's page on the site. |
{
"object": "list",
"state": "NJ",
"data": [
{
"county": "Bergen",
"state": "NJ",
"upcoming_sales": 212,
"next_30_days": 64,
"held_last_90_days": 51,
"next_sale_date": "2026-10-15",
"url": "https://www.sheriffsaledata.com/sheriff-sales/new-jersey/bergen"
}
],
"data_as_of": "2026-10-12"
}The key’s account: its plan, what the API opens for it and, when that is nothing, why; its limits, its usage and the key itself, by name and last four characters. It answers any key that works, whatever the plan, and carries no email address. Free.
{
"object": "account",
"plan": "Pro",
"trial": false,
"status": "active",
"api": {
"keys": true,
"records": true,
"reason": null
},
"limits": {
"keys": 2,
"requests_per_minute": 60,
"requests_per_day": 5000,
"in_flight": 2,
"records_per_day": 1000,
"records_per_month": 3000,
"max_page_size": 200
},
"usage": {
"month": {
"limit": 3000,
"csv": 120,
"api": 721,
"used": 841,
"remaining": 2159,
"resets_at": "2026-11-01T00:00:00Z"
},
"day": {
"limit": 1000,
"used": 211,
"remaining": 789,
"frees_at": "2026-10-13T09:12:44.512Z"
}
},
"keys": {
"live": 1,
"max": 2
},
"key": {
"name": "CRM sync",
"last4": "a1B2",
"created_at": "2026-10-12T14:05:00.000Z",
"last_used_at": "2026-10-12T14:20:00.000Z"
}
}https://www.sheriffsaledata.com/api/v1 lists the endpoints, and /openapi.json describes them in OpenAPI 3.1. Neither needs a key, and both may be read from any origin: they carry no one’s data.
A sale carries the CSV export’s columns, under the same names and in the same order, with its status_history after rescheduled_from, then when we first and last saw it, its record page and its other ids. Every field is always present.
There is no separate endpoint for a sale’s history: every dated status the sheriff posted, each postponement, adjournment, stay and result, is in its own status_history, newest first. In New Jersey the opening_bid is the sheriff’s approximate upset price where the list prints one; many sheriffs announce it only at the sale, so it is often null.
| Field | Type | What it holds |
|---|---|---|
id | string | The sale's id: the record currently representing it. It can change when a better record wins the sale; any id it has had still finds it. |
sale_type | string | What is being foreclosed: a mortgage, unpaid tax, an association's lien, a money judgment, or unstated. One of mortgage_foreclosure, tax_foreclosure, hoa_foreclosure, judgment_execution, foreclosure. |
state | string | Two-letter state code. |
county | string, or null | The county, spelled as the county filter wants it. |
address | string, or null | The property's street address as listed. |
city | string, or null | The town or city as listed. |
zip | string, or null | The ZIP code as listed. |
parcel_id | string, or null | The parcel number as listed. |
sale_date | string (date), or null | The sale date: the newest one, if the sale was postponed. |
sale_time | string, or null | The time of the sale as listed. |
sale_timezone | string, or null | The time zone of the sale's time. |
sale_status | string, or null | scheduled, listed, sold, unsold or in_progress. Stayed, cancelled and paid-off sales are not returned. |
status_detail | string, or null | The sheriff's latest status words, as posted. |
postponements | integer, or null | How many times the sheriff posted a postponement or adjournment. |
rescheduled_from | string (date), or null | The date the sale was last moved from. |
status_history | array of strings | Every dated status the sheriff posted, newest first, each as "YYYY-MM-DD: status". |
opening_bid | number, or null | The opening bid or upset price in US dollars, where the listing prints one. |
judgment_amount | number, or null | The judgment in US dollars, as listed (excludes interest since judgment and costs). |
appraised_value | number, or null | The sheriff's appraisal in US dollars, where the state requires one. |
deposit_required | string, or null | The deposit the sheriff requires, as listed. |
sold_amount | number, or null | What the property sold for, once the sheriff reports it. |
sold_to | string, or null | Whether the plaintiff or a third party bought it, where reported. |
case_number | string, or null | The court case number. |
sheriff_number | string, or null | The sheriff's own number for the sale. |
plaintiff | string, or null | The plaintiff, as listed. |
defendant | string, or null | The defendant, as listed. |
plaintiff_attorney | string, or null | The plaintiff's attorney, as listed. |
sale_subtype | string, or null | A finer kind of sale, where the listing names one. |
notes | string, or null | Other notes the listing prints. |
legal_description | string, or null | The legal description as listed. |
sale_venue | string, or null | Where the sale is held, or the government auction site. |
sale_format | string, or null | In person or online. |
first_seen_at | string (date-time) | When we first saw the sale: the order for incremental sync (sort=first_seen). |
last_seen_at | string (date-time) | When a listing last showed it. |
url | string (uri) | The sale's record page on the site. |
other_ids | array of strings | The sale's other record ids. An id you hold that is here is this sale. |
Lists are paged by cursor. Ask for up to 200 sales with limit, then pass each page’s next_cursor back as cursor, with the same filters and sort, until it is null. A cursor resumes exactly after the last sale it was given, so sales added or dropped by a refresh while you walk never make it skip or repeat one (a sale whose date or id changes mid-walk, adjourned or listed again, can move past the cursor; the weekly re-pull below catches it). A cursor sent with other filters is 400 invalid_cursor. You may change limit between pages.
status) in the order we first saw them, to the end, keeping each sale by its id: /sales?state=NJ&sort=first_seen.first_seen_after set a few minutes before the newest first_seen_at you hold. A sale you already have comes back free.first_seen_at. Sales fetched this month cost nothing to fetch again.Ids can change. When a better listing of a sale takes it over, the office’s own sale list joining another listing of it or a resale under the same case, the sale’s id changes. other_ids lists the ids of its other listings, so an id you hold that appears there is this sale, and /sales/{id} still finds a sale by an id it used to have. Sales, counts and coverage carry data_as_of, the date of the newest data behind them.
Every error has the same shape, so a client needs one branch: a code to act on, a message for people, the param at fault when there is one, details where they help, a doc_url to its row below and the request_id. On a 429 or a 503, wait Retry-After seconds and repeat the request, except on quota_exceeded, which means stop until the allowance comes back.
{
"error": {
"code": "invalid_parameter",
"message": "sale_date_from must be a real date written YYYY-MM-DD, such as 2026-10-31.",
"param": "sale_date_from",
"doc_url": "https://www.sheriffsaledata.com/sheriff-sale-data-api#error-invalid_parameter",
"request_id": "req_0123456789abcdef01234567"
}
}| Code | Status | When | What to do |
|---|---|---|---|
invalid_parameter | 400 | A query parameter or path segment has a value the API does not accept, or one is sent twice. param names it. | Fix the value; the message says what is accepted. |
unknown_parameter | 400 | A query parameter this endpoint does not take. Parameters are strict, so a misspelt filter is an error rather than ignored. | Check the spelling against the endpoint's parameter list. |
invalid_cursor | 400 | The cursor is damaged, was issued for different filters or sort, or was signed under a key the site no longer uses. | Repeat the request with the original parameters, or start again without a cursor. |
key_in_url | 400 | An API key appeared in the web address. Addresses are written to access logs, so a key found in one is revoked. | Create a new key and send it only in the Authorization header. |
unauthorized | 401 | No Authorization: Bearer header was sent. A website sign-in cookie does not authenticate the API. | Send Authorization: Bearer <key>. |
invalid_key | 401 | The key is malformed, unknown, revoked, expired after 180 days unused, or belongs to a closed account; details.reason says which. | Check that the key was copied whole, or create a new one on the account page. |
forbidden_plan | 403 | The account's plan does not include the data API, or not this part of it. | See details.upgrade_url for the plans that include it. |
trial_limit | 403 | The account is in its free trial, and the data API opens with the first payment. | Start the plan now from the account page (details.start_now_url), or wait for the trial to end. |
payment_past_due | 403 | The last payment did not go through, so the API is paused. The keys are kept. | Update the card from the account page; the API resumes when the payment goes through. |
plan_inactive | 403 | The account's plan is not active, so the API is closed. | Contact us if this is unexpected. |
not_found | 404 | No sale, state or endpoint by that id or path. | Search again: a sale's id can change when a newer record takes it over. |
method_not_allowed | 405 | The data API is read-only and answers GET only. | Use GET. |
rate_limited | 429 | Too many requests: per minute, per day, at once, or from an address sending keys that do not work. details.window says which. | Wait for Retry-After seconds, then repeat the request. |
quota_exceeded | 429 | The records allowance is spent and every sale on the page would be new: the month's (shared with CSV export) or the 24-hour ceiling. details.limit says which. | Stop, and resume after Retry-After seconds. Sales already fetched this month can still be fetched. |
server_error | 500 | Something went wrong on our side. | Retry later; quote the request_id if it keeps happening. |
busy | 503 | The API is answering as many requests as it can, or the database took too long. | Wait for Retry-After seconds, then repeat the request. |
api_disabled | 503 | The API is paused for maintenance. Keys and allowances are kept. | Retry after Retry-After seconds. |
Each walks every page of a search, waits out a 429 or a 503 for as long as Retry-After says, and stops when the allowance is spent. Each reads the key from SHERIFFSALEDATA_API_KEY.
curl "https://www.sheriffsaledata.com/api/v1/sales?state=NJ&status=upcoming&limit=100" \
-H "Authorization: Bearer $SHERIFFSALEDATA_API_KEY"import os, time, requests
BASE = "https://www.sheriffsaledata.com/api/v1"
session = requests.Session()
session.headers["Authorization"] = "Bearer " + os.environ["SHERIFFSALEDATA_API_KEY"]
def sales(**filters):
"""Every sale matching the filters, a page at a time."""
params = {"limit": 200, **filters}
while True:
response = session.get(f"{BASE}/sales", params=params, timeout=60)
if response.status_code in (429, 503):
error = response.json()["error"]
if error["code"] == "quota_exceeded":
raise SystemExit(error["message"]) # the allowance is spent: stop
time.sleep(int(response.headers.get("Retry-After", "5")))
continue
response.raise_for_status()
page = response.json()
yield from page["data"]
if not page["next_cursor"]:
return
params["cursor"] = page["next_cursor"]
for sale in sales(state="OH", status="upcoming"):
print(sale["id"], sale["sale_date"], sale["county"], sale["case_number"], sale["opening_bid"])// Node 18 or later, from a server or a scheduled job: never a web page.
const BASE = "https://www.sheriffsaledata.com/api/v1";
const headers = { Authorization: `Bearer ${process.env.SHERIFFSALEDATA_API_KEY}` };
const sleep = (seconds) => new Promise((resolve) => setTimeout(resolve, seconds * 1000));
async function* sales(filters) {
const params = new URLSearchParams({ limit: "200", ...filters });
while (true) {
const response = await fetch(`${BASE}/sales?${params}`, { headers });
const body = await response.json();
if (response.status === 429 || response.status === 503) {
if (body.error.code === "quota_exceeded") throw new Error(body.error.message);
await sleep(Number(response.headers.get("retry-after") ?? 5));
continue;
}
if (!response.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
yield* body.data;
if (!body.next_cursor) return;
params.set("cursor", body.next_cursor);
}
}
for await (const sale of sales({ state: "PA", status: "upcoming" })) {
console.log(sale.id, sale.sale_date, sale.county, sale.case_number, sale.opening_bid);
}// Google Sheets: Extensions > Apps Script. Save the key once, under
// Project Settings > Script properties, as SHERIFFSALEDATA_API_KEY: it stays in the
// script, out of the sheet. Run importSales, or give it a daily trigger.
const BASE = "https://www.sheriffsaledata.com/api/v1";
const COLUMNS = ["id", "state", "county", "city", "address", "sale_date", "sale_status", "case_number", "plaintiff", "opening_bid", "judgment_amount", "url"];
function importSales() {
const key = PropertiesService.getScriptProperties().getProperty("SHERIFFSALEDATA_API_KEY");
const book = SpreadsheetApp.getActiveSpreadsheet();
const sheet = book.getSheetByName("Sales") || book.insertSheet("Sales");
const rows = [COLUMNS];
let cursor = "";
while (true) {
const query = "state=NJ&status=upcoming&limit=200" + (cursor ? "&cursor=" + encodeURIComponent(cursor) : "");
const response = UrlFetchApp.fetch(BASE + "/sales?" + query, {
headers: { Authorization: "Bearer " + key },
muteHttpExceptions: true,
});
const status = response.getResponseCode();
const body = JSON.parse(response.getContentText());
if (status === 429 || status === 503) {
if (body.error.code === "quota_exceeded") break; // the allowance is spent: keep what came
const headers = response.getHeaders(); // named as the server sent them: lower case
Utilities.sleep(1000 * Number(headers["retry-after"] || headers["Retry-After"] || 5));
continue;
}
if (status !== 200) throw new Error(body.error.code + ": " + body.error.message);
body.data.forEach((sale) => rows.push(COLUMNS.map((name) => (sale[name] === null ? "" : sale[name]))));
cursor = body.next_cursor;
if (!cursor) break;
}
sheet.clearContents();
sheet.getRange(1, 1, rows.length, COLUMNS.length).setValues(rows);
}Apps Script fetches from Google’s servers, so the key never reaches the sheet’s viewers; but anyone who can edit the script can read its properties, so share editing only with people you would give the key to.
A sale carries what CSV export carries, including the parties the court case names: the plaintiff, the defendant and the plaintiff’s attorney. The API finds sales by place and property, never by person: q matches addresses, towns, ZIP codes, parcel numbers, case numbers and sheriff’s numbers, and never a defendant or a plaintiff, and the plaintiff filter takes only an organization with a page on the site, never a private person. Legal descriptions are not searched either: some print the owner, or a court case’s whole caption.
Each record is a copy of a sale as the office holding it listed it when we last read its sale list, and not an authoritative record of any sale, debt or title: sales are adjourned, stayed or cancelled after they are listed, sometimes on the morning of the sale. Confirm the date, time, place, opening bid and terms with the sheriff’s office or the court before you act. The data is not a consumer report, and the terms forbid using it to decide anyone’s credit, insurance, employment or housing, or to harass or defraud anyone named in it.
Your keys are yours alone: use them from systems you control, within your plan’s limits, and keep them out of web pages and addresses. You may use the data in your own business; you may not resell or redistribute it in bulk, or use it to build a database or a service for others. The full text is in the terms, and what we store about keys and their use is in the privacy policy.
No. Like CSV export, the data API opens with the first payment: during the 3-day free trial no key can be created. To use it the day you sign up, start your plan from your account page ("Start Pro now", under Subscription): the trial ends, the first payment is taken at checkout, and the API and CSV export open as soon as it goes through. The trial is then cancelled, so it is never charged as well.
No. Through the API a sale counts once a calendar month (UTC), the first time any of your keys fetches it. Fetching it again that month is free, even once the allowance is spent, so a daily sync costs each sale once a month. Counts, coverage and your usage are always free.
On the sale itself. status_history lists every dated status the sheriff posted, newest first; postponements counts the postponements and adjournments; and rescheduled_from is the date the sale last moved from. There is no separate endpoint for it, so it costs nothing beyond the sale.
No. The API answers JSON only, and there is no format parameter. CSV export is part of the website: run a search while signed in and export it. It draws on the same monthly allowance, and it does not accept API keys.
Because a key in a web page is a published key: anyone who opens the page can read it and spend your allowance. The data endpoints send no CORS headers, so browsers refuse their answers. Call the API from a server, a script or a scheduled job, and give your own pages the results.
Plans are for working your markets: the API's allowance is the one CSV export already has. For more than that, such as a full historical extract or a licensed feed into a product of your own, write to us through the contact form under "Bulk data, exports or an API".