Developers
The PartsAndPlanes API
Everything a seller does on PartsAndPlanes, from your own system: push inventory from your ERP or a spreadsheet, pull the RFQs buyers send you and answer them, get the open DLA solicitations that match your stock, read part and NSN intelligence, and receive webhooks when something happens. REST over HTTPS, JSON in and out.
Authentication
Create a key under Business Settings → API Access and send it in the X-API-Key header on every request. Keys act as your account: anything the key uploads is listed under your company, RFQs it answers are your quotes.
curl https://endpoint.partsandplanes.com/v1/me \ -H "X-API-Key: pnp_live_…"
Inventory
/v1/inventory— upload or update parts — JSON or CSVSend a JSON array (or { "parts": [...] }), or post a CSV with Content-Type: text/csv. Column names are matched loosely, so a PartsBase or ILSmart export works as-is (see column names). Each part is matched on part number + condition under your account: existing rows are updated, new ones created. Up to 20,000 parts per request; send batches for more. Parts from sellers not yet verified are held for review until verification, exactly like a spreadsheet upload.
curl -X POST https://endpoint.partsandplanes.com/v1/inventory \
-H "X-API-Key: pnp_live_…" -H "Content-Type: application/json" \
-d '{"parts": [
{"part_no": "AN3-17A", "condition": "NE", "qty": 100, "price": 0.30, "pack_qty": 100,
"description": "BOLT, MACHINE", "manufacturer": "MS", "nsn": "5306-00-151-0777", "uom": "EA",
"traceability": "8130-3", "alt_part": "MS20074-03-17"},
{"pn": "96-380038-25", "cond": "AR", "qty": 1, "price": 25.00, "desc": "Beechcraft master cylinder"}
]}'
# or a CSV straight from your export
curl -X POST https://endpoint.partsandplanes.com/v1/inventory \
-H "X-API-Key: pnp_live_…" -H "Content-Type: text/csv" --data-binary @inventory.csv{ "received": 2, "ok": 2, "failed": 0, "warnings": 0, "errors": [] }Rows that fail come back in errors with the row number and reason; the rest still go through. A price of 999,999.99 or higher is treated as “no price” (a common placeholder).
/v1/inventory?page=1&limit=100&q=AN3— list your live parts/v1/inventory/{part_no}?condition=NE— remove a listing (all conditions if none given)Removal is what you want when stock hits zero and you don't want the listing shown; to keep it listed at zero, upload it with qty: 0 instead.
Column names we understand
Case, spaces, dashes and dots don't matter. Left is what we store; right is what you can send.
| part_no | part_number, partnumber, pn, p/n, mpn, oem_pn, mfr_part, item, sku |
| quantity | qty, qty_available, qoh, on_hand, stock, available |
| condition_code | condition, cond, cc, cond_code (NE, NS, OH, SV, AR, RP, US) |
| description | desc, nomenclature, item_name, name, title |
| unit_price | price, unitprice, list_price, sell_price, asking_price (USD, per pack) |
| alt_part | alt, alternate, alternate_part, cross_ref, xref, internal_pn, stock_number |
| nsn | national_stock_number, niin (13 digits, or 9-digit NIIN; dashes ignored) |
| manufacturer | mfr, mfg, brand, oem, make |
| uom | unit, unit_of_measure, ui, unit_of_issue (default EA) |
| traceability | trace, tag, cert, certification, paperwork |
| pack_qty | pack, pack_size, units_per_pack, unit_qty, multiple (default 1) |
| is_negotiable / location / serial_number / aircraft_type / currency / active | negotiable · warehouse, site · serial, sn · aircraft · currency · active |
RFQs & quotes
/v1/rfqs?box=received|sent|all&stage=requested|quoted|accepted|proforma|complete— your RFQ inbox/v1/rfqs/{rfq_id}— one RFQ with the buyer, the part, and price intelligence/v1/rfqs/{rfq_id}/quote— send or revise your quotecurl -X POST https://endpoint.partsandplanes.com/v1/rfqs/1042/quote \
-H "X-API-Key: pnp_live_…" -H "Content-Type: application/json" \
-d '{"price": 24.99, "qty": 1, "lead_days": 5, "valid_days": 14, "condition": "AR",
"terms": "Net 30 · FOB origin", "note": "8130-3 available"}'The buyer is emailed and texted the moment you quote. When they accept or decline you get the rfq.accepted / rfq.declined webhook, and the RFQ moves to the pro-forma step on the site.
/v1/rfqs/{rfq_id}/decision— as the buyer: { "decision": "accepted" | "declined", "reason": "…" }Government demand
/v1/gov/matches— open DLA solicitations for NSNs you listEvery part you upload is cross-referenced to its NSN overnight. This returns the open DIBBS solicitations on those NSNs, with quantity, unit, due date, the AMSC code (and whether it's restricted to approved sources), and — if your CAGE is on file — whether you are an approved source. The same matches drive the twice-daily email/SMS alerts and the gov.match webhook.
{ "count": 19, "cage": "9EEV9", "matches": [
{ "solicitation": "SPE4A626U4047", "nsn": "5306001510777", "item": "BOLT,MACHINE", "qty": 1753, "ui": "EA",
"due": "2026-09-22", "amsc": "G", "restricted": false, "you_are_approved_source": false,
"your_part_no": "AN3-17A", "match": "xref-mfr", "url": "https://partsandplanes.com/rfq/SPE4A626U4047" } ] }Spreadsheets: Excel and Google Sheets
One fact about one part number, as plain text, so it drops straight into a cell. No key; the record is public. q is a part number or an NSN; f is the field. Values are cached ten minutes; 3,000 cells an hour from one address (the API above has higher limits).
/feed/cell?q=MS20470AD4-6&f=last_paid— one value as text/feed/cell.csv?q=MS20470AD4-6— every field as a two-line CSVFields: found, nsn, item, last_paid, last_date, buys_36m, open_dla, open_sam, approved_sources, sellers, ads, flis_price, googled_28d, url. An empty cell means the record has no value (no award with a price, say), not an error.
Excel (Windows and Microsoft 365; the part number in A2):
=VALUE(WEBSERVICE("https://endpoint.partsandplanes.com/feed/cell?q="&ENCODEURL(A2)&"&f=last_paid"))
=WEBSERVICE("https://endpoint.partsandplanes.com/feed/cell?q="&ENCODEURL(A2)&"&f=nsn")
=VALUE(WEBSERVICE("https://endpoint.partsandplanes.com/feed/cell?q="&ENCODEURL(A2)&"&f=open_dla"))Google Sheets, the quick way: one call per row brings every field into the columns to the right.
=IMPORTDATA("https://endpoint.partsandplanes.com/feed/cell.csv?q="&ENCODEURL(A2))Or a PNP() function: Extensions → Apps Script, paste this, save, then =PNP(A2, "last_paid") works like any other formula.
function PNP(part, field) {
field = field || "last_paid";
var url = "https://endpoint.partsandplanes.com/feed/cell?q=" + encodeURIComponent(part) + "&f=" + encodeURIComponent(field);
var t = UrlFetchApp.fetch(url, { muteHttpExceptions: true }).getContentText().trim();
return (t !== "" && !isNaN(t)) ? Number(t) : t;
}Data, not advice: last_paid is the unit price on the most recent DLA award for the stock number, with its date in last_date; quantity, packaging and terms differed on that award. LibreOffice Calc has the same WEBSERVICE function.
Intelligence
/v1/parts/{part_no}/intel— the Part Intelligence dossier: NSN identities, approved sources, government purchase history, demand, market/v1/nsn/{niin}/intel— NSN-level pricing history, seasonality, cadence, competitors, bid oddsWebhooks
Register an HTTPS URL and we POST JSON to it when things happen. Manage them under Business Settings → Webhooks or via the API.
/v1/webhooks— list/v1/webhooks— { "url": "https://…", "events": ["rfq.received", …] } → returns the signing secret once/v1/webhooks/{id}— remove/v1/webhooks/{id}/test— send a pingEvents
rfq.received— a buyer sent you an RFQ (rfq_id, part_no, qty, lead_days, url)rfq.quoted— a seller quoted an RFQ you sent (price, qty, total, validity, terms)rfq.accepted/rfq.declined— the buyer decided on your quote (reason included when declined)gov.match— new DLA solicitations match your inventory (up to 25 per delivery, after each harvest)
Verifying a delivery
Each POST carries X-PnP-Event and X-PnP-Signature = hex HMAC-SHA256 of the raw body using your secret. Reply 2xx within 10 seconds; after 50 consecutive failures the webhook is paused.
// Node
const crypto = require("crypto")
const expected = crypto.createHmac("sha256", process.env.PNP_WEBHOOK_SECRET).update(rawBody).digest("hex")
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers["x-pnp-signature"]))
# Python
import hmac, hashlib
ok = hmac.compare_digest(hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest(), request.headers["X-PnP-Signature"]){ "event": "rfq.received", "created_at": "2026-09-10T14:02:11Z",
"data": { "rfq_id": 1042, "part_no": "96-380038-25", "qty": 1, "lead_days": 9, "url": "https://partsandplanes.com/rfq?c=1042" } }Coming from PartsBase?
Point your integration at https://endpoint.partsandplanes.com and use your PartsAndPlanes API key as the client_secret. The token call is identical, and every endpoint also accepts the key directly in X-API-Key — no token dance required.
/connect/token— grant_type=password&client_id=…&client_secret=YOUR_KEY&scope=api&username=…&password=…curl -X POST https://endpoint.partsandplanes.com/connect/token -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=password&client_id=myapp&client_secret=pnp_live_…&scope=api"
{ "access_token": "pnp_live_…", "expires_in": 31536000, "token_type": "Bearer" }
curl https://endpoint.partsandplanes.com/v1/me -H "Authorization: Bearer pnp_live_…"| PartsBase service | Here | What you get on top |
|---|---|---|
| Authentication Service | POST /connect/token | identical; or just send the key in X-API-Key |
| Parts Market Pricing | GET /prod-pbd-marketpricing?partnumber= | identical valuesPerCode shape, plus a government block (FLIS, DLA-paid median, open solicitations) |
| Send RFQs | POST /api-sendrfq · GET /api-sendrfq?rfqId= | identical request/response; InventoryId is our listing id; the seller is texted, emailed and pushed |
| Parts Batch Search | POST /api/inventoryAvailabilities | your existing zip (manifest + data file) works as-is; answers in one call instead of three, with a requestId and CSV download; JSON and CSV bodies accepted too |
| Push RFQs / Response RFQs | webhook, format: partsbase | we POST the exact PushRfq / PushRfqResponse JSON to your existing receiver — set the webhook format to PartsBase |
| Parts Batch Search | POST /v1/parts/search | NSN identities, open DLA solicitations, FLIS and DLA-paid prices per part, cheapest live offers with seller CAGE |
| Government Data Batch Search | POST /v1/gov/search | approved sources with SAM status, 5-year award history with top vendors, open solicitations with AMSC restriction, our market on that NSN |
| Inventory Upload | POST /v1/inventory | JSON or raw CSV, PartsBase column names accepted as-is, per-row errors, pack sizes |
| Parts Market Pricing | POST /v1/pricing/parts | min / median / max by condition, seller and quantity counts, market vs DLA % |
| MRO Market Pricing | POST /v1/pricing/mro | overhauled / repaired / serviceable pricing vs new, overhaul-capable sellers |
| — | GET /v1/rfqs · POST …/quote | your live RFQ inbox and quoting, from your system |
| — | GET /v1/gov/matches | DLA solicitations that match what you list, with approved-source flag |
| — | Webhooks | rfq.received / quoted / accepted / declined, gov.match — signed |
Exact-path aliases
# PartsBase market pricing, same shape
curl "https://endpoint.partsandplanes.com/prod-pbd-marketpricing?partnumber=AN3-17A" -H "Authorization: Bearer pnp_live_…"
{ "valuesPerCode": [ { "conditionCode": "NE", "lastUpdated": "2026-09-08T10:12:00 EST", "maxUnitPrice": "0.45", "medianUnitPrice": "0.30", "minUnitPrice": "0.28", "partNumber": "AN3-17A" } ],
"government": { "in_catalog": true, "niins": ["001510777"], "open_dla_rfqs": 1, "dla_median_36m": 0.37, "flis_price": 0.41 } }
# PartsBase send-RFQ, same shape (InventoryId = the listing id from /v1/parts/search or the listing page)
curl -X POST https://endpoint.partsandplanes.com/api-sendrfq -H "Authorization: Bearer pnp_live_…" -H "Content-Type: application/json" -d '{"Parts":[{"InventoryId":25213,"Quantity":1,"UoM":"EA"}],"LeadTime":7,"RFQTrackingNo":"PO-4471","Comments":"Need 8130-3","MRO":false}'
{ "rfqId": 1043, "rfqIds": [1043], "rfqNotQualifiedList": [] }
curl "https://endpoint.partsandplanes.com/api-sendrfq?rfqId=1043" -H "Authorization: Bearer pnp_live_…"
{ "RFQItemId": 1043, "Status": "Quoted", "Items": [ { "RFQItemId": 1043, "Status": "Quoted", "UnitPrice": 24.99, "QuantityAv": 1, "UoM": "EA" } ] }# PartsBase batch search: post the same Filename.zip (manifest.xml + fixed-width data file)
curl -X POST https://endpoint.partsandplanes.com/api/inventoryAvailabilities -H "Authorization: Bearer pnp_live_…" -F "[email protected]"
{ "requestId": 88, "status": "Completed", "rows": 2, "found": 1, "downloadUrl": "…/api/inventoryAvailabilities/88/csv",
"results": [ { "lineIndex": "1", "partNumber": "311", "description": "Bearing, Ball, Annular", "condition": "NE", "found": true,
"availability": { "listings": 3, "sellers": 2, "qty_available": 40, "min": 18.50, "median": 22.00, "max": 31.00 },
"availabilityMatchesCondition": true, "byCondition": { … }, "offers": [ … ], "government": { … } } ] }
# fetch it again later, or as a spreadsheet
GET /api/inventoryAvailabilities/88 GET /api/inventoryAvailabilities/88/csvReceiving RFQs the PartsBase way: add a webhook with format: "partsbase" and we deliver rfq.received as the exact PushRfq body (BuyerCompany, BuyerAddress…, RfqLineItems[…]) and rfq.quoted as PushRfqResponse (with PriceType, Traceability, QuantityAv, UoM, UnitPrice). Your existing receiver needs no changes beyond the signature check.
Batch search & pricing
/v1/parts/search— { "part_numbers": ["AN3-17A", "96-380038-25", …] } — up to 500{ "count": 2, "with_listings": 2, "results": [
{ "part_number": "AN3-17A", "url": "https://partsandplanes.com/search/AN3-17A",
"market": { "listings": 4, "sellers": 3, "qty_available": 2600, "min": 0.28, "median": 0.30, "max": 0.45, "last_listed": "2026-09-08" },
"by_condition": { "NE": { "listings": 4, "min": 0.28, "median": 0.30, "max": 0.45 } },
"cheapest_offers": [ { "condition": "NE", "unit_price": 0.28, "qty": 1000, "uom": "EA", "pack_qty": 100, "seller": "Leading Edge Group, LLC", "cage": "9EEV9", "traceability": "8130-3" } ],
"government": { "in_catalog": true, "niins": ["001510777"], "item_name": "BOLT,MACHINE", "open_dla_rfqs": 1, "next_due": "2026-09-22",
"flis_price": 0.41, "flis_ui": "EA", "dla_median_36m": 0.37, "dla_awards_36m": 3, "dla_last_award": "2026-08-30" } } ] }/v1/gov/search— { "nsns": ["5306-00-151-0777", …] } or { "part_numbers": […] } — up to 200 NSNsPer NSN: item name, formatted NSN, approved sources (CAGE, part number, RNCC, SAM status), FLIS price and unit, five years of awards (count, median/min/max unit price, total value, by year, top vendors, last award and vendor), open solicitations with AMSC and set-aside, and how many PartsAndPlanes sellers list it.
/v1/pricing/parts— { "part_numbers": […] } — market price by condition plus the government price basis and market-vs-DLA %/v1/pricing/mro— { "part_numbers": […] } — OH / RP / SV / AR pricing against new for the same part, and overhaul-capable sellersLimits & errors
1,000 requests per key per day by default (X-RateLimit-Remaining on every response); inventory uploads count as one request regardless of size. Need more? Say so from your dashboard and we'll raise it. Errors are JSON with an error field and the usual status codes: 400 bad input, 401 bad or missing key, 403 not allowed (for example quoting an RFQ that isn't yours), 404, 429 over quota.
Questions, or a format we don't read yet? Tell us — we add aliases the same day.