Skip to main content
One call accepts the BOM, then you poll a second for the report. This page covers the shape of both, how to read a report while it is still processing, and what comes back for a line we could not score.

The call

parts[].mpn is the only required field. manufacturer, supplier and description are used when present; a line sent without supplier is flagged no_supplier (see the gap table below). quantity is stored and returned on the line; it does not affect the score today. name is a label shown back in the list. Idempotency-Key is optional and set by your code; see below. One call carries up to 500 lines. Two of the gap flags are relative to the whole BOM (a line is “single source” or “concentrated” only in relation to the other lines), so splitting a BOM across calls changes what those flags report. For a BOM over 500 lines, upload it as a file instead: a file carries up to 2,000 rows in one BOM. The call answers 202 at once, with two headers: Location, the absolute URL of the report, and Retry-After: 5, the seconds to wait before polling it.
Poll GET /v1/bom/{id} until bom.status is complete; every five seconds is a good interval, and a read while processing is cheap.

Retrying safely

If a call times out you cannot tell whether the BOM was stored. Have your code generate a new Idempotency-Key (a UUID) for each submission and send it as a header; when your code retries that same request, it sends the same key. If the first request got through, the retry gets that BOM back with 202 instead of a second copy, and its summary says the key was already used. A key is recognised only from the same API key that sent it, so separate API keys never share keys. The request body is not compared, which is why the key must come from your code rather than from a person or a model. The header is optional: without it, a retried submission creates a second BOM. Reuse a key only to retry the same submission. A new run of a BOM, or an updated version of it, is a new call with a new key or none, and gets a new id. To see current scores for a BOM you already submitted, read it again: scores are computed when you read. POST /v1/bom/upload takes the same header.

Uploading a file instead

If the BOM is a spreadsheet, send it as-is to POST /v1/bom/upload as multipart/form-data, with the file in file and an optional name. We find the part-number column and the columns we recognize, such as manufacturer, quantity, supplier and description, then run the same analysis. A row whose part-number cell is blank is skipped, so bom.lines can be lower than the row count of the file; the JSON call, by contrast, rejects a line without mpn.
CSV and XLSX are accepted; name defaults to the file name. One file carries up to 2,000 rows, and a request body over 4.5 MB is refused with 413, without the usual error body. From here on the two paths are the same: the report, the list and the delete below apply to an uploaded BOM as well.

What comes back

Trimmed for length: notice is present on every response, the other two lines are omitted, and distributors is cut to its first entry. Every field is listed in the reference. as_of is when the response was built. Scores are computed at that moment, so it is the date the figures are good for. country is optional: the buyer’s country as a two-letter ISO 3166-1 code. The scores are then computed for a buyer there, and scored_for_country names it. Without it the scores are not tied to any buyer location and scored_for_country is null. A value that is not a country code answers 400.

Reading it without parsing it

summary is one sentence: the line count, how many lines fall in each tier, and which drivers are at medium or above on how many lines, always in the same order. When any line is high or critical, it says how many and points to GET /v1/parts for them. If you are relaying to a person or a model, that line is enough on its own.

The BOM

risk.average is the mean of the scored lines’ composites, on the same 0–100 scale as the lines themselves; its interpretation says how many lines it averages. risk.distribution counts every line exactly once, so its five numbers add up to bom.lines; not_scored covers lines still pending and lines we could not score. risk.gaps lists each kind of gap present in this BOM with how many scored lines carry it; a kind with no lines is not listed. countries says where the BOM is made, most lines first. share_percent is a share of all lines, including lines with no known origin, so the shares need not add up to 100. Lines with no known origin are not listed. worst_tier is the highest tier among that country’s scored lines, or null when none is scored.

Each line

parts is in the order you submitted, with line starting at 1. part.identified_mpn is set only when the part number we identified differs from the one you sent. risk.composite combines the four drivers under risk.drivers. Each driver is a band (tier) and a sentence, with no number; the composite is the number: A driver with no data is null. The line’s risk.tier is the band its composite falls in, and each driver’s tier is that driver’s band, all from low, medium, high and critical. Every interpretation opens with its band. gap_flags uses five values: supply has the same shape as the single-part endpoint: stock and lead times as measures with units, a null with a reason where nothing is quoted, and lead_time_min / lead_time_max as the 25th and 75th percentiles of the quoted lead times (the middle half of quotes), returned as null when that middle half is a single figure, since there is no range to report. distributor_count is the number of distributors holding stock; distributors lists those holding the most, largest first. supply.scope says whose listings the figures count: manufacturer when they are this line’s manufacturer’s, all_manufacturers when the line names no manufacturer, and other_manufacturers when none is listed for the named manufacturer, so the figures are other makers of the same part number. It is null when there are no figures. events lists the shipping conditions and up to five reported events that most affect the line’s disruption_exposure, strongest first: ordered by effect on this line’s score, not by severity, which is how serious the event or slowdown is in itself. Each item has a kind (news, port_activity or sea_route) and the same shape as disruption_events on a part report; id and url are null for shipping conditions, and url can be null on news too. Smaller influences are not listed, so a line can score above 0 with events empty.
A line’s score is its risk within this BOM. GET /v1/parts scores the part on its own, so the same part can score differently on the two.

Line status

status is one of four values. resolved means the part was identified with a manufacturer or a confident part number, and it carries a score unless no distributor has stock or lead-time figures for it; that line has no overall score, its reason says so, and the drivers that could be measured are still shown. low_confidence means we identified something about it, at least its category, but not enough to stand behind a score. unresolved means it could not be identified; its reason reads “The part could not be identified, so no score is computed.” When the analysis could not run on a line at that moment, the line is unresolved with the reason “This line could not be analyzed right now. Submit it again later.” pending means it is still being worked, with the reason “Still being analyzed.” bom.identified counts identified lines, so both resolved and low_confidence; bom.unresolved counts unresolved. risk.distribution.not_scored counts every line without a score, whichever of the three statuses it has.

Notes on a line

We never change what you submitted. When supplier catalogues disagree with a line, the line keeps your values and carries a note in notes saying what was found:
A line is scored only on a part number you submitted. When a line carries an internal code, a description or an incomplete part number, it is not scored under a guessed part: it gets a part_number_needed or part_not_listed note instead, and the rest of the BOM is analyzed as usual. Such a line returns what you submitted and the note, nothing else: its risk.drivers are all null, events and gap_flags are empty, its part fields are null, and it counts only under not_scored. For a BOM uploaded as a file, the note’s summary names the cell to fix, for example “Line 2 (cell A3): … Add the manufacturer part number in cell A3 of your file”. Add the part numbers and submit the BOM again, or research one part on its own with GET /v1/parts.
A note about a catalogue’s answer is only written when a catalogue answered, so a temporary supplier outage never produces one. notes is always present and empty when there is nothing to note. When any line has a note, the report’s summary says how many.

Reading a report while it is processing

GET /v1/bom/{id} answers at any time. While bom.status is processing, the lines already analyzed carry their scores and the rest are pending with no score, drivers, gap flags or events; risk, countries and summary are computed from the analyzed lines only, and summary starts with “Processing:”. Because single_source and concentration_risk are relative to the other lines, a finished line’s flags and gaps driver can still move as the remaining lines settle. When every line is identified but origin and supply are still being gathered, summary says so. status becomes complete only once every line’s origin and supply are in; then completed_at is set and bom.identified plus bom.unresolved equal bom.lines. Scores are computed when you read, against the disruption events and distributor figures current at that moment, so two reads of the same BOM can differ.

Listing and deleting

GET /v1/bom lists your organization’s BOMs, newest first, 50 per page. Set limit for a different page size, from 1 to 100. When next_cursor is not null, call again with cursor set to it for the next page; null means you have them all.
To read every BOM, loop until next_cursor is null:
A BOM is kept until you delete it. DELETE /v1/bom/{id} removes it and every line, and answers with the id it removed:
A BOM belongs to the organization whose key submitted it. Reading or deleting one with another organization’s id answers 404, the same as an id that does not exist.

From BOM to part

A BOM report tells you which lines need attention; the part report tells you what to do about one. Take the high and critical lines and call GET /v1/parts with each line’s mpn, adding manufacturer when the line has one. The part report adds alternates, a datasheet, the events behind the score and recommended actions. Its score is the part’s own, so expect it to differ from the line’s.

When the request is not right

A line without a part number: 400

The path names the first line that failed, counting from zero: parts.0 is the line a report would call line: 1. The whole BOM is rejected and nothing is stored or analyzed: fix that line and send the BOM again. An empty parts array and a quantity that is not a positive whole number answer the same way.

A page request that is not right: 400

limit outside 1–100, or a cursor that is not a next_cursor we returned, answers 400 invalid_request with a summary saying which. Pass next_cursor back unchanged.

A file with no part-number column: 422

A file that is not CSV or XLSX, or is empty, answers 400 invalid_request naming the accepted types. A file over 2,000 rows answers 422 bom_too_large with the cap and the rows found.

More than 500 lines in one call: 422

Nothing is stored. Send the whole BOM as a file rather than in halves, for the reason the summary gives. A file over 2,000 rows is the one case where splitting is the answer; that 422 says so.

No such BOM: 404

The analysis could not be started: 502

Nothing was stored, so sending the same BOM again creates no duplicate, with or without an Idempotency-Key. On the upload path the same code and status are answered when the column detector did not respond, with reason “The column detector did not respond.”

Research a part

The single-part report; each line’s supply figures share its shape.

Errors

Every status code and what to do about it.