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.

Get an API keyManage webhooks

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

POST/v1/inventory— upload or update parts — JSON or CSV

Send 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).

GET/v1/inventory?page=1&limit=100&q=AN3— list your live parts
DELETE/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_nopart_number, partnumber, pn, p/n, mpn, oem_pn, mfr_part, item, sku
quantityqty, qty_available, qoh, on_hand, stock, available
condition_codecondition, cond, cc, cond_code (NE, NS, OH, SV, AR, RP, US)
descriptiondesc, nomenclature, item_name, name, title
unit_priceprice, unitprice, list_price, sell_price, asking_price (USD, per pack)
alt_partalt, alternate, alternate_part, cross_ref, xref, internal_pn, stock_number
nsnnational_stock_number, niin (13 digits, or 9-digit NIIN; dashes ignored)
manufacturermfr, mfg, brand, oem, make
uomunit, unit_of_measure, ui, unit_of_issue (default EA)
traceabilitytrace, tag, cert, certification, paperwork
pack_qtypack, pack_size, units_per_pack, unit_qty, multiple (default 1)
is_negotiable / location / serial_number / aircraft_type / currency / activenegotiable · warehouse, site · serial, sn · aircraft · currency · active

RFQs & quotes

GET/v1/rfqs?box=received|sent|all&stage=requested|quoted|accepted|proforma|complete— your RFQ inbox
GET/v1/rfqs/{rfq_id}— one RFQ with the buyer, the part, and price intelligence
POST/v1/rfqs/{rfq_id}/quote— send or revise your quote
curl -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.

POST/v1/rfqs/{rfq_id}/decision— as the buyer: { "decision": "accepted" | "declined", "reason": "…" }

Government demand

GET/v1/gov/matches— open DLA solicitations for NSNs you list

Every 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).

GET/feed/cell?q=MS20470AD4-6&f=last_paid— one value as text
GET/feed/cell.csv?q=MS20470AD4-6— every field as a two-line CSV

Fields: 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

GET/v1/parts/{part_no}/intel— the Part Intelligence dossier: NSN identities, approved sources, government purchase history, demand, market
GET/v1/nsn/{niin}/intel— NSN-level pricing history, seasonality, cadence, competitors, bid odds

Webhooks

Register an HTTPS URL and we POST JSON to it when things happen. Manage them under Business Settings → Webhooks or via the API.

GET/v1/webhooks— list
POST/v1/webhooks— { "url": "https://…", "events": ["rfq.received", …] } → returns the signing secret once
DELETE/v1/webhooks/{id}— remove
POST/v1/webhooks/{id}/test— send a ping

Events

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.

POST/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 serviceHereWhat you get on top
Authentication ServicePOST /connect/tokenidentical; or just send the key in X-API-Key
Parts Market PricingGET /prod-pbd-marketpricing?partnumber=identical valuesPerCode shape, plus a government block (FLIS, DLA-paid median, open solicitations)
Send RFQsPOST /api-sendrfq · GET /api-sendrfq?rfqId=identical request/response; InventoryId is our listing id; the seller is texted, emailed and pushed
Parts Batch SearchPOST /api/inventoryAvailabilitiesyour 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 RFQswebhook, format: partsbasewe POST the exact PushRfq / PushRfqResponse JSON to your existing receiver — set the webhook format to PartsBase
Parts Batch SearchPOST /v1/parts/searchNSN identities, open DLA solicitations, FLIS and DLA-paid prices per part, cheapest live offers with seller CAGE
Government Data Batch SearchPOST /v1/gov/searchapproved sources with SAM status, 5-year award history with top vendors, open solicitations with AMSC restriction, our market on that NSN
Inventory UploadPOST /v1/inventoryJSON or raw CSV, PartsBase column names accepted as-is, per-row errors, pack sizes
Parts Market PricingPOST /v1/pricing/partsmin / median / max by condition, seller and quantity counts, market vs DLA %
MRO Market PricingPOST /v1/pricing/mrooverhauled / repaired / serviceable pricing vs new, overhaul-capable sellers
—GET /v1/rfqs · POST …/quoteyour live RFQ inbox and quoting, from your system
—GET /v1/gov/matchesDLA solicitations that match what you list, with approved-source flag
—Webhooksrfq.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/csv

Receiving 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

POST/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" } } ] }
POST/v1/gov/search— { "nsns": ["5306-00-151-0777", …] } or { "part_numbers": […] } — up to 200 NSNs

Per 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.

POST/v1/pricing/parts— { "part_numbers": […] } — market price by condition plus the government price basis and market-vs-DLA %
POST/v1/pricing/mro— { "part_numbers": […] } — OH / RP / SV / AR pricing against new for the same part, and overhaul-capable sellers

Limits & 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.