Looking for our new API? Get started for free here.

Nexar
API
Have questions? Chat with us at our Developer Forum or send us an email (api@octopart.com).

BOM Quickstart


As of March 31, 2022 Octopart APIv3 will no longer be supported. As of June 30, 2022 this version will be completely disabled.

Octopart APIv3 and Nexar Legacy API REST provide methods which make it easy to match line items in a BOM against Octopart parts and retrieve information about pricing and availability. In this tutorial we will walk you throgh building a Python script which parses a BOM, matches it against Octopart parts, and prices the components. We'll provide sample Python code along the way. For the purposes of this tutorial, we'll be working with a publicly available Arduino BOM. For simplicity, we've already converted the BOM into a CSV formatted file and made it available for download here.

Parsing a BOM

We'll need to convert our BOM into queries which the API recognizes. We'll be using the parts/match endpoint. This endpoint can accept a variety of parameters to match on, we'll be using the 'mpn' and the 'brand' of the manufacturer, though we could also leave out the 'brand' or use the 'sku' instead. The code snippet below uses the Python CSV DictReader to iterate through the BOM specified on the command line and converts line items into queries.

  • # Section 1
    # Convert csv file into line items and queries
    # This code assumes a file format similar to the one on the
    # Arduino BOM
    import csv
    
    
    csv_file = open("arduino_bom.csv", "r")
    csv_reader = csv.DictReader(csv_file)
    line_items = []
    queries = []
    for line_item in csv_reader:
        # Skip line items without part numbers and manufacturers
        if not line_item['Part Number'] or not line_item['Manufacturer']:
            continue
        line_items.append(line_item)
        queries.append({'mpn': line_item['Part Number'],
                        'brand': line_item['Manufacturer'],
                        'reference': len(line_items) - 1})
    
  • namespace OctopartApi
    {
        using CsvHelper;
        using Newtonsoft.Json;
        using RestSharp;
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.IO;
        using System.Web.Script.Serialization;
    
        public static class BomQuote
        {
            public static void ExecuteSearch()
            {
                // Section 1
                // Convert CSV file into line items and queries
                // This code assumes a file format similar to the one on the
                // Arduino BOM
                List<Dictionary<string, string>> line_items = new List<Dictionary<string, string>>();
                List<Dictionary<string, string>> queries = new List<Dictionary<string, string>>();
                using (StreamReader reader = File.OpenText("arduino_bom.csv"))
                {
                    using (var csv = new CsvReader(reader))
                    {
                        while (csv.Read())
                        {
                            var mpn = csv.GetField<string>("Part Number");
                            var brand = csv.GetField<string>("Manufacturer");
                            if (!string.IsNullOrEmpty(mpn) && !string.IsNullOrEmpty(brand))
                            {
                                line_items.Add(Enumerable.Range(0, csv.FieldHeaders.Length).ToDictionary(i => csv.FieldHeaders[i], i => csv.CurrentRecord[i]));
                                queries.Add(new Dictionary<string, string> { 
                                    {"mpn", mpn},
                                    {"brand", brand},
                                    {"reference", (line_items.Count - 1).ToString()}
                                });
                            }
                        }
                    }
                }
    
  • Imports CsvHelper
    Imports Newtonsoft.Json
    Imports RestSharp
    Imports System
    Imports System.Collections.Generic
    Imports System.Linq
    Imports System.IO
    Imports System.Web.Script.Serialization
    
    Public Module BomQuote
        Public Sub ExecuteSearch()
            ' Section 1
            ' Convert CSV file into line items and queries
            ' This code assumes a file format similar to the one on the
            ' Arduino BOM
            Dim line_items = New List(Of Dictionary(Of String, String))()
            Dim queries = New List(Of Dictionary(Of String, String))()
            Using reader As StreamReader = File.OpenText("arduino_bom.csv")
                Using csv As CsvReader = New CsvReader(reader)
                    While csv.Read()
                        Dim mpn As String = csv.GetField("Part Number")
                        Dim brand As String = csv.GetField("Manufacturer")
                        If (Not String.IsNullOrEmpty(mpn) And Not String.IsNullOrEmpty(brand)) Then
                            line_items.Add(Enumerable.Range(0, csv.FieldHeaders.Length).ToDictionary(Of String, String)(Function(i) csv.FieldHeaders(i), Function(i) csv.CurrentRecord(i)))
                            queries.Add(New Dictionary(Of String, String)() From _
                                            {{"mpn", mpn}, _
                                             {"brand", brand}, _
                                             {"reference", (line_items.Count - 1).ToString()}})
                        End If
                    End While
                End Using
            End Using
    

Matching Line Items

Now that we've aggregated our Octopart APIv3 formatted queries, we'll send them to the API for matching to Octopart parts. As the parts/match endpoint can only accept twenty line items at a time, we'll batch our queries into groups of twenty and aggregate the results.

  • # Section 2
    # Send queries to REST API for part matching.
    import json
    import urllib
    
    
    results = []
    for i in range(0, len(queries), 20):
        # Batch queries in groups of 20, query limit of
        # parts match endpoint
        batched_queries = queries[i: i + 20]
    
        url = 'http://octopart.com/api/v3/parts/match?queries=%s' \
            % urllib.quote(json.dumps(batched_queries))
        url += '&apikey=REPLACE_ME'
        data = urllib.urlopen(url).read()
        response = json.loads(data)
    
        # Record results for analysis
        results.extend(response['results'])
    
  • namespace OctopartApi
    {
        using CsvHelper;
        using Newtonsoft.Json;
        using RestSharp;
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.IO;
        using System.Web.Script.Serialization;
    
        public static class BomQuote
        {
            public static void ExecuteSearch()
            {
                // Section 2
                // Send queries to REST API for part matching
                List<dynamic> results = new List<dynamic>();
                for (int i = 0; i < queries.Count; i+=20)
                {
                    // Batch queries in groups of 20, query limit of
                    // parts match endpoint
                    var batched_queries = queries.GetRange(i, Math.Min(20, queries.Count - i));
    
                    string octopartUrlBase = "http://octopart.com/api/v3";  // Octopart API url
                    string octopartUrlEntpoint = "parts/match";             // Octopart search type
                    string apiKey = APIKEY;                             // -- your API key -- (https://octopart.com/api/register)
                
                    // Create the search request
                    string queryString = (new JavaScriptSerializer()).Serialize(batched_queries);
                    var client = new RestClient(octopartUrlBase);
                    var req = new RestRequest(octopartUrlEntpoint, Method.GET)
                                .AddParameter("apikey", apiKey)
                                .AddParameter("queries", queryString);
    
                    // Perform the search and obtain results
                    var data = client.Execute(req).Content;
                    var response = JsonConvert.DeserializeObject<dynamic>(data);
                    results.AddRange(response["results"]);
                }
    
  • Imports CsvHelper
    Imports Newtonsoft.Json
    Imports RestSharp
    Imports System
    Imports System.Collections.Generic
    Imports System.Linq
    Imports System.IO
    Imports System.Web.Script.Serialization
    
    Public Module BomQuote
        Public Sub ExecuteSearch()
            ' Section 2
            ' Send queries to REST API for part matching
            Dim results As New List(Of Object)
            For i As Integer = 0 To queries.Count Step 20
                ' Batch queries in groups of 20, query limit of
                ' parts match endpoint
                Dim batched_queries = queries.GetRange(i, Math.Min(20, queries.Count - i))
                Dim octopartUrlBase As String = "http://octopart.com/api/v3"    ' Octopart API url
                Dim octopartUrlEntpoint As String = "parts/match"    ' Octopart search type
    
                ' -- your API key -- (https:'octopart.com/api/register)
                Dim apiKey As String = BomQuote.APIKEY
    
                ' Create the search request
                Dim queryString = (New JavaScriptSerializer()).Serialize(batched_queries)
                Dim client = New RestClient(octopartUrlBase)
                Dim req = New RestRequest(octopartUrlEntpoint, Method.GET) _
                                .AddParameter("apikey", apiKey) _
                                .AddParameter("queries", queryString)
    
                ' Perform the search and obtain results
                Dim data = client.Execute(req).Content
                Dim response = JsonConvert.DeserializeObject(data)
                results.AddRange(response("results"))
            Next
    

Analyzing Results

Now for the big payoff! We iterate through our results, keeping track of line items which didn't match on an Octopart part via the 'reference' parameter. For parts that did match, we'll extract a price in US dollars averaged across all offers at the price break for the specified quantity in the line item. If desired, we could extend this analysis further to find the minimum price on all components, favor particular distributors or even isolate supply shortages.

  • # Section 3
    # Analyze results sent back by Octopart API
    from decimal import Decimal
    
    
    print "Found %s line items in BOM." % len(line_items)
    # Price BOM
    hits = 0
    total_avg_price = 0
    for result in results:
        line_item = line_items[result['reference']]
        if len(result['items']) == 0:
            print "Did not find match on line item %s" % line_item
            continue
    
        # Get pricing from the first item for desired quantity
        quantity = Decimal(line_items[result['reference']]['Qty'])
        prices = []
        for offer in result['items'][0]['offers']:
            if 'USD' not in offer['prices'].keys():
                continue
            price = None
            for price_tuple in offer['prices']['USD']:
                # Find correct price break
                if price_tuple[0] > quantity:
                    break
                # Cast pricing string to Decimal for precision
                # calculations
                price = Decimal(price_tuple[1])
            if price is not None:
                prices.append(price)
    
        if len(prices) == 0:
            print "Did not find pricing on line item %s" % line_item
            continue
        avg_price = quantity * sum(prices) / len(prices)
        total_avg_price += avg_price
        hits += 1
    
    print "Matched on %.2f of BOM, total average price is USD %.2f" % ( \
        hits / float(len(line_items)), total_avg_price)
    
  • namespace OctopartApi
    {
        using CsvHelper;
        using Newtonsoft.Json;
        using RestSharp;
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.IO;
        using System.Web.Script.Serialization;
    
        public static class BomQuote
        {
            public static void ExecuteSearch()
            {
                // Section 3
                // Analyze results sent back Octopart API
                Console.WriteLine("Found " + line_items.Count + " line items in BOM.");
                // Price BOM
                int hits = 0;
                double total_avg_price = 0.0;
                foreach (var result in results)
                {
                    var line_item = line_items[(int)result["reference"]];
                    if (!result["items"].HasValues)
                    {
                        Console.WriteLine(String.Format("Did not find a match on line item #{0} ({1})", (int)result["reference"] + 1, string.Join(" ", line_item.Values.ToArray<string>())));
                        continue;
                    }
    
                    // Get pricing from the first item for desired quantity
                    int quantity = int.Parse(line_item["Qty"]);
                    List<double> prices = new List<double>();
                    foreach (var offer in result["items"][0]["offers"])
                    {
                        if (offer["prices"]["USD"] == null)
                            continue;
                        double price = 0;
                        foreach (var price_tuple in offer["prices"]["USD"])
                        {
                            // Find correct price break
                            if (price_tuple[0] > quantity)
                                break;
                            price = price_tuple[1];
                        }
                        if (price != 0)
                        {
                            prices.Add(price);
                        }
                    }
    
                    if (prices.Count == 0)
                    {
                        Console.WriteLine(String.Format("Did not find USD pricing on line item #{0} ({1})", (int)result["reference"] + 1, string.Join(" ", line_item.Values.ToArray<string>())));
                        continue;
                    }
    
                    double avg_price = quantity * prices.Sum() / prices.Count;
                    total_avg_price += avg_price;
                    hits++;
                }
    
                Console.WriteLine(String.Format("Matched on {0:0.0}% of BOM, total average prices is USD {1:0.00}.", (hits / (float)line_items.Count) * 100, total_avg_price));
            }
    
            private const string APIKEY = "REPLACE_ME";
        }
    }
    
  • Imports CsvHelper
    Imports Newtonsoft.Json
    Imports RestSharp
    Imports System
    Imports System.Collections.Generic
    Imports System.Linq
    Imports System.IO
    Imports System.Web.Script.Serialization
    
    Public Module BomQuote
        Public Sub ExecuteSearch()
            ' Section 3
            ' Analyze results sent back Octopart API
            Console.WriteLine("Found " & line_items.Count & " line items in BOM.")
            ' Price BOM
            Dim hits As Integer = 0
            Dim total_avg_price As Double = 0.0
            For Each result In results
                Dim line_item As Object = line_items(result("reference"))
                If (Not result("items").HasValues) Then
                    Console.WriteLine(String.Format("Did not find a match on line item #{0} ({1})", result("reference") + 1, String.Join(" ", CType(line_item, Dictionary(Of String, String)).Select(Function(kvp) kvp.Value).ToArray())))
                    Continue For
                End If
    
                ' Get pricing from the first item for desired quantity
                Dim quantity As Integer = Integer.Parse(line_item("Qty"))
                Dim prices = New List(Of Double)()
                For Each offer In result("items")(0)("offers")
                    If (offer("prices")("USD") Is Nothing) Then
                        Continue For
                    End If
    
                    Dim price As Double = 0
                    For Each price_tuple In offer("prices")("USD")
                        ' Find correct price break
                        If (price_tuple(0) > quantity) Then
                            Exit For
                        End If
                        price = price_tuple(1)
                    Next
    
                    If (Not price = 0) Then
                        prices.Add(price)
                    End If
                Next
    
                If (prices.Count = 0) Then
                    Console.WriteLine(String.Format("Did not find USD pricing on line item #{0} ({1})", result("reference") + 1, String.Join(" ", CType(line_item, Dictionary(Of String, String)).Select(Function(kvp) kvp.Value).ToArray())))
                    Continue For
                End If
    
    
                Dim avg_price As Double = quantity * prices.Sum() / prices.Count
                total_avg_price += avg_price
                hits = hits + 1
            Next
    
            Console.WriteLine(String.Format("Matched on {0:0.0}% of BOM, total average prices is USD {1:0.00}.", (hits / line_items.Count) * 100, total_avg_price))
        End Sub
    
        Private Const APIKEY As String = "REPLACE_ME"
    End Module
    

Putting all of this code together, running the script results in the following output (this assumes 'arduino_bom.csv' is in the same directory as the Python script).

    
$ python bom_quote.py

Found 47 line items in BOM.
Matched on 0.83 of BOM, total average price is USD 37.68
    

The final assembled script is available for download here.