KEH API

America's biggest used camera store, as JSON: the grade, the stock count and the price

The KEH API returns keh.com, the United States' largest managed used photo and video gear store, as clean JSON in five actions: search, product, facets, categories and suggest.

no credit card1,000 free credits · instant API key · pay by card or crypto
Missing a KEH endpoint, or need a source we don't have yet?Contact us real people · same-day reply.
K
/keh/v1

5 active endpoints, on 1 and 2 credit tiers.

  • POST/keh/v1/search
  • POST/keh/v1/product
  • POST/keh/v1/facets
  • POST/keh/v1/categories
  • POST/keh/v1/suggest

What KEH endpoints does ReefAPI ship?

5 live read endpoints. Read-only data API: no writes, no account actions, no dashboard access on the target site.

5 endpoints

search

2 cr

Search or browse KEH's catalogue.

required
—
optional
query, brand, category, grade, system, product_type, format, lens_mount, megapixels, film_type, filter_size, filter_type, zoom_or_prime, focus_type, lens_type, max_aperture, memory_card, lens_design, flash_system, photography_type, tag, gear_group, in_stock, price_min, price_max, focal_length_min, focal_length_max, sort, page, page_size, include_pii

product

2 cr

The complete record of ONE product by its pid (or by its keh.com url).

required
—
optional
pid, url, include_pii

facets

1 cr

The live filter taxonomy of KEH's catalogue.

required
—
optional
facets, query, brand, category, grade, system, product_type, format, lens_mount, megapixels, film_type, filter_size, filter_type, zoom_or_prime, focus_type, lens_type, max_aperture, memory_card, lens_design, flash_system, photography_type, tag, gear_group, in_stock, price_min, price_max, focal_length_min, focal_length_max, include_pii

categories

1 cr

KEH's own category tree.

required
—
optional
query, brand, category, grade, system, product_type, format, lens_mount, megapixels, film_type, filter_size, filter_type, zoom_or_prime, focus_type, lens_type, max_aperture, memory_card, lens_design, flash_system, photography_type, tag, gear_group, in_stock, price_min, price_max, focal_length_min, focal_length_max, include_pii

suggest

1 cr

KEH's own search autocomplete.

required
query
optional
count, include_pii

Every parameter, every allowed value →

KEH API

5 of 5 endpoints, ready to run

View docs ↗

Every KEH product that matches, with KEH's own price, how many units are in stock, whether that price is a live offer, the brace shorthand KEH prints in its own titles pulled out as its own field, the image and the direct product URL. In-stock only unless you ask otherwise.

2 credits0 required · 30 optional
POST/keh/v1/search
idle
// Press "Try it" and this pane shows exactly what the
// live site returned this second — including an empty
// result, if that is the truth. No key, no account.

How the KEH API works

KEH is a normal ReefAPI surface — the same four rules that hold for every other engine on the key.

01
Authenticate
x-api-key header

No OAuth app, no request signing, no per-site account. One key covers all 438 engines.

02
Call
POST /keh/v1/…

Every route is a POST with a JSON body. Parameters are validated against the published schema before anything is charged.

03
Pay
1 or 2 credits per call

Credits, not seats. Failed and blocked calls are never charged, and cache hits cost nothing.

04
Read
{ ok, data, meta, error }

One envelope everywhere. meta carries latency_ms, record_count and the endpoint that answered.

From a camera name to the exact gear KEH has on the shelf

Three calls. The first finds the filter values KEH itself uses, the second narrows the catalogue with them, the third opens one item.

01facets { facets: "brand,grade,lens_mount" }
POSTfacets { facets: "brand,grade,lens_mount" }

KEH's own live value lists with its own counts, so the filter you build next uses values the source will accept instead of values you guessed.

02search { query: "canon eos r6", sort: "price_asc" }
POSTsearch { query: "canon eos r6", sort: "price_asc" }

The catalogue rows: pid, title, brand, price, units_in_stock and the product URL. meta.total_results is KEH's own count for the query, so a filter that did nothing is visible.

03product { pid: "384010" }
POSTproduct { pid: "384010" }

One item in full: which of KEH's grades it currently holds, the price band those grades span, the description and the spec rows.

A priced, in-stock comparable set for one body or lens, with KEH's own condition grade attached to every row.

request
curl -X POST https://api.reefapi.com/keh/v1/search \
  -H "x-api-key: $REEF_KEY" \
  -H "content-type: application/json" \
  -d '{"query":"canon eos r6","sort":"price_asc"}'
response envelope
{
  "ok": true,
  "data": { … },
  "meta": {
    "api": "keh",
    "endpoint": "search",
    "mode": "live",
    "latency_ms": …,
    "record_count": …
  },
  "error": null
}

KEH's eight condition grades, and how much stock each one had on 2026-10-08

The grade names are KEH's own, carried verbatim; the value is what you pass to the grade filter, and the rank is a normalised best-to-worst number you can sort and threshold on. A grade exists only on gear KEH actually has in stock — on a sold-out record KEH drops it — so these counts are the in-stock catalogue. A product can hold several grades at once, which is why the grades add up past the 7,553 in-stock products.

grade (KEH's own name)grade valuerankproducts in stock
Newnew0567
Like Newlike_new1194
Like New Minuslike_new_minus21,740
Excellent Plusexcellent_plus32,199
Excellentexcellent44,218
Bargainbargain51,892
Uglyugly6505
As Isas_is718

KEH's short forms are accepted too: LN, LN-, EX+, EX, BGN, UG, As-Is. Several grades can be OR-ed in one call. The product action returns grades_available for one model, ranked best to worst, so you can see which rungs of the ladder KEH is holding right now.

What was measured, and what the numbers mean

KEH is a single US shop in USD: there is no market parameter and no second currency. Everything below was measured against KEH's own pages and its own counts.

Every filter was checked against KEH's own count

All 25 filters were verified twice: once as a facet, reading KEH's own value list and counts, and once as a filter whose result count had to equal the count the facet claimed. 25 of 25 matched exactly, including the long tail — a lens mount KEH's facet said held one product returned exactly one. Against the 7,553 in-stock products: Canon 783, Canon plus Nikon 1,688, Bargain 1,892, Full Frame 35mm 2,034, Canon EF 437, prime 1,897, lens-motor autofocus 1,457, Telephoto / Long 593, f/2.8 589, Camera Bodies 656, Overstock 1,538, over 2,000 dollars 299, under 50 dollars 2,762, 400 mm and up 68. Values on one field are OR-ed and the sum is exact; different fields are AND-ed, Canon plus Excellent giving 562 against 2,983 and 4,218.

The price is the cheapest grade's, and we say so

KEH's catalogue index carries one price per product while its product page offers several grades at several prices. Checked against KEH's own pages: a Canon EOS R6 body indexed at 1,352.00 is sold at 1,352 Excellent, 1,469 Excellent Plus and 1,527 Like New Minus; a Nikon Z8 indexed at 3,179.00 sits at the low end of its page's 3,138 to 3,181 band. So the figure is published as both price and price_from, grades_available shows the rungs, and the index's own price_range — which was [price, price] on all 1,600 rows sampled, spanning nothing — is returned only as price_range_index beside price_range_is_grade_blind rather than passed off as a ladder. On one of four items checked the index and the page disagreed by 175.00 and that disagreement is in our run log rather than averaged away.

A sold-out row's price is not a live offer

46,946 of the 54,499 indexed products are sold out, and on those rows KEH's unit count is 0, the grade is gone and the price is stale — one Leica M6 is indexed at 1,266.00 with no grade while KEH's own page still shows a Bargain copy at 3,200.00. So in_stock defaults to true, every row carries units_in_stock and price_is_live_offer, and asking for a grade together with in_stock=false returns zero rows with a warning explaining why instead of looking like empty stock.

The unit count is a real number, not a cap

Over 800 rows, with the in-stock filter the minimum unit count is 1 and 0 never appears once; without it 0 is the single most common value. 83 distinct counts, maximum 264, with no clustering at a ceiling — so this is a genuine quantity rather than a figure that saturates. It is KEH's own number and the page that would corroborate it is behind KEH's bot protection, which is stated rather than glossed over.

Five of KEH's own filter values do not work, and we refuse them by name

Five values in KEH's taxonomy carry a stray trailing space in KEH's data and cannot be filtered on at all: both spellings, and a URL-encoded space, returned zero across the whole 54,499-product index in the same run in which a clean-label sibling of the same field matched normally (35mm roll 2,633, IR - Infrared 77, SDHC 1,698). That is 321 products the taxonomy advertises but cannot reach. The API leaves them out of its published value lists, refuses them with that explanation, and marks them filterable: false in the facets response.

A keyword that matches nothing still returns rows

KEH's search relaxes instead of answering empty: zzqqxxnotathingqq returned 1,217 products, qwertyasdfzxcv 870, a three-word nonsense phrase 4. The relevance score does not separate that fallback from a real hit (520.9 against 505.3) and KEH's own precision metadata reports the identical value in both cases, so there is no honest flag to compute and none is invented — it is stated on the query parameter and in meta.notes, and suggest is the fix. A precise model name is exact: canon eos r6 157, canon eos r6 mark ii black body 13.

Paging is capped by the source, and the cap is reported

KEH's search service allows at most 200 rows per call and a start offset of at most 10,000, which it states in its own refusal — 10,200 rows is the deepest any one query goes. meta.pagination carries depth_limit, reachable_results and depth_limit_reached, and asking past the cap is refused as an invalid parameter before a request is spent, with the advice to split by category, brand or price band. Paging itself is honest: pages one, two and three at 50 rows had zero overlapping products in either direction, so there is no silent repeat of the last page.

There is no seller, because KEH is the seller

KEH buys gear in, inspects and grades it in its own facility and resells it with its own warranty, so the public product page carries no third-party merchant, no seller profile and no seller rating. Nothing is masked here; there is no seller entity on this source to publish. What stands in its place is KEH's own grade vocabulary and its unit count.

What KEH does not publish on this surface

No per-grade price, no serial number, no per-item inspection note and no stated warranty length. The grades a model is held in are published, their individual prices are not — per_grade_prices_available says so rather than leaving you to discover it. The description is present on 84 percent of rows and is cleaned of the commented-out markup KEH's own pages carry. Everything else — product id, title, brand, price, unit count, URL and image — was filled on 100 percent of 800 rows sampled across eight different queries.

What people build with KEH

The jobs this data is most often used for.

5

endpoints

1/2

credits per call

01

Price the US used-camera market by condition: pull every in-stock body of one brand with KEH's grade mix and price from, and see what Bargain costs against Excellent Plus on the same gear.

02

Build a lens finder that actually filters the way photographers think: mount, prime or zoom, focal-length range, maximum aperture, filter thread and whether the autofocus needs a motor in the lens or in the body.

03

Track supply and condition mix in used photo gear with KEH's own counts: 7,553 in stock of 54,499 indexed, 4,218 Excellent, 1,892 Bargain, 505 Ugly on 2026-10-08.

04

Source film gear by format and film type: 4,948 used film cameras, 2,915 large-format items, 2,633 that take 35mm roll, 278 that take 120 — filterable, with prices.

What KEH data costs

The cheapest call here is 1 credit, so $15/mo (Pro) buys 10,000 of them — $1.50 per 1,000 credits. Credits roll over and never expire, and failed or blocked calls are not charged.

Full pricing →
$0.67–$1.50 / 1,000 credits
  • 1,000 free credits on signup, no card
  • One key, all 438 APIs, one credit pool
  • Failed and blocked calls are never charged
  • Credits roll over and never expire

Call it in two lines

Sign up, get 1,000 credits and one key that works on every engine. Then this is the whole protocol.

curl
curl -X POST https://api.reefapi.com/keh/v1/search \
  -H "x-api-key: $REEF_KEY" \
  -H "content-type: application/json" \
  -d '{"query":"canon eos r6","sort":"price_asc"}'
python
import requests

r = requests.post(
    "https://api.reefapi.com/keh/v1/search",
    headers={"x-api-key": REEF_KEY},
    json={
  "query": "canon eos r6",
  "sort": "price_asc"
},
)
print(r.json()["data"])
FAQ

Have a question? We got answers.

The questions people actually ask before wiring up KEH.

Get a free key →
Is the price the one on the page?▾

It is KEH's own catalogue price, and we measured exactly what it means rather than assuming. KEH's catalogue index carries ONE price per product while a product page offers several condition grades at several prices, and checked against KEH's own pages the index price is the CHEAPEST grade's: the Canon EOS R6 body indexed at 1,352.00 is offered at 1,352 Excellent, 1,469 Excellent Plus and 1,527 Like New Minus. So the API publishes that figure twice — price, which is what KEH's listing shows, and price_from, which says what it means — and publishes grades_available so you can see which grades exist. The per-grade prices are not on this surface and per_grade_prices_available says so. The index also publishes a price_range field, and that field is a fake: it was [price, price] on all 1,600 rows we sampled, so it is returned only as price_range_index beside price_range_is_grade_blind rather than passed off as a grade ladder. All prices are plain US dollars; 1352.0 is 1,352.00 dollars, not cents.

Why does it only return in-stock gear by default?▾

Because 46,946 of KEH's 54,499 indexed products are sold out, and on a sold-out record the price left in the index is stale and the grade is gone. One measured example: a Leica M6 indexed at 1,266.00 with no grade, while KEH's own page still showed a Bargain copy at 3,200.00. Returning those by default would make 86 percent of every result set unbuyable and some of it wrong. So in_stock defaults to true, every row carries units_in_stock and price_is_live_offer, and you can pass in_stock=any or in_stock=false to get the sold-out records deliberately — they are genuinely useful as a what-KEH-handles list and as price history, as long as you know which they are.

How many units does KEH have, really?▾

The API returns units_in_stock, KEH's own unit count, and we checked that it is a real number rather than a figure that saturates at a round cap. Over 800 rows: with the in-stock filter the minimum is 1 and 0 never appears once, without it 0 is the single most common value, and there are 83 distinct counts with a maximum of 264 and no clustering at any ceiling. 0 and 'in stock' never occur together. The product page that would corroborate the number is behind KEH's own bot protection, so this is KEH's figure as KEH publishes it, and that is said rather than glossed over.

How do I know a filter actually did something?▾

Every response carries meta.total_results, KEH's own match count for your exact request, plus meta.filters_applied. More than that: all 25 filters were verified by asking KEH's facet block what a value should count and then re-querying that value as a filter — 25 of 25 matched exactly, including the long tail, where a lens mount with exactly one product returned exactly one. Against the 7,553 in-stock products in one run: Canon 783, Canon plus Nikon 1,688 (the exact sum, because values on one field are OR-ed), Bargain grade 1,892, Full Frame 35mm 2,034, Canon EF mount 437, prime lenses 1,897, autofocus with a lens motor 1,457, telephoto 593, f/2.8 589, camera bodies 656, Overstock 1,538, over 2,000 dollars 299, under 50 dollars 2,762, reaching past 400 mm 68. Different fields are AND-ed: Canon plus Excellent grade is 562 against 2,983 and 4,218. Two things the source does silently and the API refuses to: an unknown filter value returns zero rows rather than an error, and an unknown sort is ignored and the unsorted order handed back — so the API validates both itself and tells you.

How do I find the right filter value instead of guessing?▾

Call facets. It returns KEH's own live taxonomy — 25 of them, every brand, grade, category, system, gear type, lens mount, format, aperture, filter size and type, card type, flash system, optical design and intended use that has stock right now, each with KEH's own count, plus the price and focal-length ranges as real numeric spans. Every value it returns is a value search accepts, and it takes the same filters as search, so you can ask for the taxonomy inside a scope: the lens mounts available under Fujifilm came back as 9 values with counts. It also marks five values filterable: false — those are values KEH's own taxonomy advertises but cannot filter on, because their labels carry a stray trailing space in KEH's data and match zero products whichever way they are spelled. The API refuses those with that explanation instead of handing you an empty success.

Can I browse by category rather than search?▾

Yes, and the categories action gives you the map first: 135 nodes with their id, name, parent, readable breadcrumb and live product count, deepest counts first — Used Camera Lenses 15,795, Accessories 18,390, Used SLR and DSLR Lenses 9,939, Used Cameras 9,248, Used Film Cameras 4,948, Tripods and Supports 4,692. The ids it returns are exactly what the category filter takes, and a parent id includes its children. Pass it any search filter and you get the tree within that scope, which answers questions a flat listing cannot: which categories hold Leica gear came back as 58 of the 135.

Does it cover film gear, or only digital?▾

Film is a first-class part of this catalogue and of this API. KEH holds 4,948 used film cameras and 2,915 large-format items, and film-specific attributes are their own filters: film_type covers 35mm roll (2,633), 120 roll (278), 220 roll, 4x5 through 11x14 sheet, Instax, APS, 110 and 126 cartridges, 8mm, 16mm and Super 8. Coverage format runs from Full Frame 35mm down to 8x10 inch and up through medium format. Flash system covers the film-era TTL protocols by name — Canon A-TTL, Minolta TTL, Hasselblad TTL pre-flash and 18 more — which is exactly what you need to match an old flash to an old body.

What does the product action add over a search row?▾

Which of KEH's grades it currently holds of that model, ranked best to worst; the full spec sheet as KEH's own catalogue describes it — system, gear type, coverage format, lens mount, megapixels, film type, filter thread and type, prime or zoom, focus type, maximum aperture, focal-length range, memory-card types, special optical design, TTL flash system and intended uses; the category breadcrumb; KEH's merchandising tags; the unit count; and the cleaned description. You can call it with KEH's product id or by pasting a keh.com product URL. A product id KEH does not index answers NOT_FOUND, not an empty success.

Is there a seller to read?▾

No, and that is the nature of this source rather than something removed. KEH buys the gear, inspects and grades it and sells it itself, so there is no third-party merchant, seller profile or seller rating anywhere on the page — the warranty and the grade are KEH's own. Nothing is masked here; there is simply no seller entity to publish.

How deep does paging go?▾

To 10,200 rows per query, and the API tells you rather than letting you find out. That is KEH's search service's own hard cap — at most 200 rows per call and a start offset of at most 10,000, which it states in its own refusal — so meta.pagination carries depth_limit, reachable_results and depth_limit_reached, and asking past the cap is refused as an invalid parameter before a request is spent, with the advice to split the query by category, brand or price band. Paging itself is honest: pages one, two and three at 50 rows had zero overlapping products in either direction, so there is no silent repeat of the last page.

What happens with a keyword that matches nothing?▾

You get rows, and you should know that before you trust a long-tail query. KEH's search relaxes instead of answering empty: zzqqxxnotathingqq returned 1,217 products, qwertyasdfzxcv 870, a three-word nonsense phrase 4. The relevance score does not separate that fallback from a real hit (520.9 against 505.3) and KEH's own precision metadata reports the identical value in both cases, so there is no honest flag for us to compute and we do not invent one — it is stated on the query parameter and repeated in meta.notes, and the fix is to call suggest first so a keyword lands on gear KEH actually carries. A precise model name is exact: canon eos r6 returned 157, canon eos r6 mark ii black body 13, hasselblad 503cw 293.

What is the KEH API?▾

KEH API is a ReefAPI endpoint group for used cameras, lenses and film gear from keh.com as json: 54,000 products, 7,500 in stock, with keh's own condition grades, 25 measured filters and the live us price. It returns live JSON through POST requests under /keh/v1.

Is the KEH API free to try?▾

Yes. ReefAPI starts with 1,000 free credits, no card required. KEH calls use the same shared credit balance as every other ReefAPI engine.

Do I need a KEH login or account?▾

No login to KEH is needed for the API response. You call ReefAPI with your x-api-key header, and the playground can run live examples before you create a production key.

Need something this API does not do?

Name the endpoint, the field, or a source we do not carry yet. We ship new APIs every week and you would be first to get the key. Real people read every message and reply the same day.

0/4000

No account needed · we reply from [email protected]

Try it on your own data before you pay anything

The call above is the real endpoint, not a recording. A free key gives you 1,000 credits, the other 437 APIs, and the same envelope everywhere.

Endpoints, parameters and credit costs on this page are read from the live catalog and cannot drift from what the API accepts. Field notes were captured on 2026-10-07.