API Docs
Three calls: get a key, upload a catalog, resolve a query.
1. Get an API key
POST /api/register with an optional { name } body creates a workspace and returns your key. It is shown once — see Get an API key for a hosted form that does this for you.
curl -X POST https://partpin.net/api/register \
-H "content-type: application/json" \
-d '{"name":"Acme Bearing Supply"}'
# => { "orgId": "...", "apiKey": "pk_...", "next": "..." }2. Upload your catalog
POST /api/catalog with either a catalog array or a raw feedCsv string. Each item needs at least id and sku; mpn, aliases,brand, supersedes and title are optional. supersedes lists the retired numbers a part replaces: a search for one answers “has been replaced by” that part (tier superseded), and an old number with two replacements is shown to verify, never picked for the buyer.
Cross-references from the columns you already have. In a CSV feed every column named for an OEM, competitor, interchange, cross-reference or equivalent number is read (for example OEM Number, Competitor Part #, Competitor 2, Timken Interchange, aliases), and each number remembers its column. A buyer who types one gets your part with the line “Matches 6203DDU (cross-reference from your catalog)” and the column it came from (match.xref). Case, dashes, spaces and a “P/N” or “Part No.” label are ignored; a number that matches only after ignoring leading zeros is shown to verify, never asserted; a number two of your parts list is shown as a choice, never picked. A Superseded By column (also replaced by, replacement, new part number) on a retired row, and supersedes on the new one, are followed to the number to order, and the chain is returned (match.chain). A number your feed does not carry answers notStocked.noCrossReference. The upload response's xref object reports the columns found, rows mapped and rows with no cross-reference.
fits lists the machine models a part is listed as used on (also read as used on, compatible with, machine, model; separated by ; or |), and ref (or item no.) is the part's number on the maker's diagram. Write G0555 @ 11 when the reference differs by model. A search for a model answers the parts your feed lists for it, tier fits, each with its reference, and only what the feed lists: nothing is inferred from a title or a neighbouring model, two models that differ by one character are never merged, and a model the feed does not list answers the closest listed models to check (nearModels), never a part.
curl -X POST https://partpin.net/api/catalog \
-H "authorization: Bearer pk_..." \
-H "content-type: application/json" \
-d '{"catalog":[{"id":"1","sku":"6203-2RS","aliases":["6203DDU"]}]}'A whole catalog goes up in chunks of at most 2,000 items: send the first chunk as above (mode defaults to replace, which clears what was there), each next chunk with "mode":"append", then one request with "mode":"finalize" and no items, which tunes the index over the full catalog. A bigger append chunk is refused with HTTP 413. Re-sending a chunk after a failed request is safe. The CLI does all of this in one command:
npx partpin login
npx partpin upload catalog.csv # or catalog.json; add --append to keep existing items3. Resolve a query
GET /api/v1/resolve — pass the query as q and your key either as ?key= or an Authorization: Bearer header. Results come back either asserted (deterministic, safe to act on) or as a ranked candidate (verify before using).
A query that no exact, alias, superseded or fits tier owns comes back with tier: "not_stocked" and a notStocked object: a plain “is not in this catalog” sentence, up to three nearest parts the shop does stock (each marked “Verify, not the same part”, never asserted), and the shop's own contact line when the shop set one with PUT /api/v1/shop (or the dashboard, or npx partpin contact). PartPin never writes a phone number for a shop, and a machine model the feed does not list gets no parts at all. Every such search is listed in your miss report, with the parts it offered, at GET /api/v1/queries?misses=1.
curl "https://partpin.net/api/v1/resolve?q=6203ddu&key=pk_..."4. Drop it into your own site
One script tag puts a working part-number search on any page, with no build step, no framework and no API key in your HTML. Paste your workspace id (the Org value on your dashboard) into data-org:
<div id="partpin"></div>
<script src="https://partpin.net/widget.js"
data-org="YOUR-ORG-ID"
data-target="#partpin"
data-placeholder="Part number"></script>The widget calls GET /api/widget/resolve?org=…&q=…, which is read-only and sends CORS headers so it works from your domain. It can do exactly one thing — resolve a part number against your own catalog, the same search your site already offers the public. It cannot upload, confirm, read your query history, or see any other workspace, which is why it needs no key: your API key carries write access and must never appear in a public page.
It shows an asserted match as the answer and un-asserted candidates as candidates to check, and says “no match” plainly when there is none — the precision doctrine, in the UI. Optional attributes: data-limit (candidates shown, default 5), data-query (resolve immediately on load, for a server-rendered search page), and data-api (point it at another deployment). Resolves through the widget count against your plan and are rate-limited per workspace and per visitor.
It sits beside your store's own search, never instead of it. The widget is one extra box next to your search form, and it makes one attempt with a 1.5 second clock. If PartPin is slow, down, or out of quota (a 5xx, a 429, a timeout, a network error), the widget submits your original search form with what the buyer typed, exactly as the browser would have: your action, your fields, your results page, your theme. No error is shown, nothing is retried, and nothing is left blank. It uses the first form[role=search] on the page; point it at another with data-form="#my-search". To keep your own results presentation, set data-render="none": the widget draws nothing and hands you the answer as a partpin:result event (detail = { q, body }, the same JSON the endpoint returns) or to a global function named by data-on-result. A failure fires partpin:fallback. A number with no cross-reference in your feed is said plainly, with a one-click “Search the shop” button that runs your own search; set data-no-match="store" to run it straight away. The only CSS it adds is under the .partpin-w class. No app-store account and no third-party account is needed for any of these:
Shopify
Online Store, Themes, Edit code, layout/theme.liquid, just before </body>:
<div id="partpin"></div>
<script src="https://partpin.net/widget.js" data-org="YOUR-ORG-ID"
data-target="#partpin" data-form="form[action='{{ routes.search_url }}']"></script>BigCommerce
Storefront, Script Manager, Create a script: location Footer, pages Storefront, type Script:
<div id="partpin"></div>
<script src="https://partpin.net/widget.js" data-org="YOUR-ORG-ID"
data-target="#partpin" data-form="form[action$='/search.php']"></script>WooCommerce
Appearance, Theme File Editor, footer.php (or any footer-scripts field), before wp_footer():
<div id="partpin"></div>
<script src="https://partpin.net/widget.js" data-org="YOUR-ORG-ID"
data-target="#partpin" data-form="form.woocommerce-product-search"></script>Other endpoints
GET /api/v1/queries?key=pk_...— recent resolve calls for your workspace; add&misses=1for zero-result / unasserted-only queries.POST /api/v1/confirm—{ query, itemId }, records that a human confirmed a candidate match. It becomes an asserted alias next time.POST /api/keys/rotate— dashboard-session only. Replaces your workspace’s API key; the old one stops working immediately.
Pricing
Sign-up, the dashboard, catalog upload and the Zero-Results Autopsy are free forever — 1,000 resolves a month on a 25,000-SKU catalog, not a trial. Beyond that: Counter $99 / month, Branch $299 / month, Chain $699 / month. Published prices, no sales call — see Pricing.