# BOM Part Availability & Lead-Time Monitor (`plusonets/bom-part-availability-monitor`) Actor

Reconcile a bill of materials with your own Mouser and DigiKey API keys. Resolves exact MPN and packaging, prices the order quantity, and reports stock, lead time, and lifecycle. Keeps a baseline and emits shortage, recovery, lead-time, and lifecycle changes. Demo mode needs no keys.

- **URL**: https://apify.com/plusonets/bom-part-availability-monitor.md
- **Developed by:** [Olalekan Johnson](https://apify.com/plusonets) (community)
- **Categories:** E-commerce, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 part-supplier observeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## BOM Part Availability & Lead-Time Monitor

Unofficial. Not affiliated with Mouser, DigiKey, or the part manufacturers.

A buyer pastes a bill of materials and their own Mouser and DigiKey API credentials. This Actor asks those official APIs whether the **exact** manufacturer part number, in the packaging on the line, can cover the quantity that must actually be ordered. It prices the break that applies at that quantity, compares distributor stock with the order, and keeps a baseline so the next run can report a shortage, a recovery, a lead-time change, or a lifecycle change.

It does not place orders. It does not treat another part as electrically equivalent. It does not scrape distributor websites.

### Who it is for

Small electronics procurement teams who already have a Mouser Search API key, a DigiKey Product Information API app, or both, and who need a repeatable shortage watch on a real BOM. A one-piece price and a reel of a different suffix are the wrong answer for a build that needs 2,000 pieces of tape-and-reel.

### Demo mode is the default

Leave every credential empty and run with the default input. `runMode` is `auto`, so the Actor uses a bundled sample response set instead of calling Mouser or DigiKey. The sample is synthetic. It is not a quote.

The default run returns **12 dataset rows** (6 sample parts × Mouser and DigiKey). Every row has `demo: true`, `charged: false`, and a `demoNotice`. Part observations are not charged. The only platform charge on a demo run is the start event, if you have enabled it.

The first demo run compares the sample with a bundled previous baseline, so the dataset contains real transition types:

| Line | Part | What the sample shows |
| --- | --- | --- |
| U1 | TI LM358DR, 2,000, tape and reel | Mouser stock 800, so `shortage`. The 1-piece price is 0.48; the price used is the 1,000-piece break, 0.15. DigiKey reel MOQ 2,500 raises the order and can fulfill. Cut tape is noted and not used. |
| C12 | Murata GRM21BR71H104KA01L, 10,000 | Mouser multiple 4,000 raises the order to 12,000. |
| U3 | ST STM32G031K8T6, 500, tray | Mouser lists tube only, so the tray constraint is unmet and the tube price is not used. DigiKey tray MOQ 160 rounds the order to 640. |
| U7 | Analog Devices AD8606ARMZ, 100, tube | Mouser lead time moved from 8 weeks to 18. DigiKey lifecycle moved from active to NRND. A replacement mentioned in the sample is not copied into the row. |
| R4 | Yageo RC0805FR-0710KL, 5,000 | Mouser was short and now has stock: `recovery`. |
| U9 | TI SN74LVC1G08DBVR, 1,000, cut tape | Mouser returns a different manufacturer and a different suffix. DigiKey has no product. Both are `empty` shortages. The other MPNs are not recommended. |

Coverage on that first run is 9 complete, 1 partial, 2 empty. A later demo run of the same sample still returns 12 rows, and the event counts drop to zero because the baseline was saved. Give each BOM its own `stateStoreName` if you do not want schedules to share a baseline.

Force this path with `runMode: "demo"` even if keys are filled in. Demo never sends those keys.

### Input

Put the BOM in `bomJson`, or `bomCsv`, or a public `bomUrl`. JSON wins, then CSV, then URL. Column names are case-insensitive. Spaces and underscores are ignored, so `Manufacturer Part Number`, `mpn`, and `lineId` all work.

```csv
lineId,manufacturer,mpn,quantity,packaging,currency
U1,Texas Instruments,LM358DR,2000,Tape & Reel,USD
```

```json
[
  {
    "lineId": "U1",
    "manufacturer": "Texas Instruments",
    "mpn": "LM358DR",
    "quantity": 2000,
    "packaging": "Tape & Reel",
    "currency": "USD"
  }
]
```

| Field | Required | What it does |
| --- | --- | --- |
| `runMode` | no | `auto` (default), `demo`, or `live`. Auto is demo until a selected supplier has credentials. |
| `suppliers` | no | Default `mouser` and `digikey`. |
| `mouserApiKey` | for live Mouser | Secret. Sent only to `api.mouser.com`. |
| `digikeyClientId`, `digikeyClientSecret` | for live DigiKey | Secrets. Used only to obtain a client-credentials token. |
| `digikeyAccountId` | no | Secret. Sent as `X-DIGIKEY-Account-Id` when your DigiKey app requires it. |
| `digikeySandbox` | no | Call `sandbox-api.digikey.com`. |
| `digikeyLocaleSite` | no | Two-letter site such as `US` or `DE`. Otherwise the site follows the line currency. Prices are still not converted. |
| `currency` | no | Default `USD` when a line omits it. |
| `alertOnlyOnChange` | no | Dataset keeps material changes only. Live mode still charges every successful check. The full set is in the `LEDGER` key. |
| `stateStoreName` | no | Named key-value store for the baseline. Default `bom-part-availability-state`. |
| `maxBomLines` | no | Default 200, maximum 500. Extra rows are counted and skipped. |
| `minRequestIntervalMs` | no | Pause between official API calls. Default 250. |

A live run with only a Mouser key checks Mouser and marks DigiKey `blocked` for missing credentials. It does not fill DigiKey from the demo. An unknown supplier name is `unsupported` and is not scraped.

`bomUrl` must be `http` or `https` on port 80 or 443, with no username or password in the URL. Localhost, `.local`, `.internal`, private, loopback, and link-local addresses are rejected, including after a redirect.

### How a row is decided

1. The MPN must match exactly. Case and internal spaces are ignored. Punctuation stays, so `SN74LVC1G08DBVR` is not `SN74LVC1G08DBVRG4`.
2. The manufacturer must match when you supplied one. `TI` matches Texas Instruments. `ST` matches STMicroelectronics. Maxim does not match Analog Devices. If you leave the manufacturer blank and two manufacturers share the MPN, the line is ambiguous and neither is chosen.
3. The packaging constraint selects only that packaging of the same MPN. Tape and reel, partial reel (Digi-Reel, MouseReel), cut tape, tray, tube, bulk, and ammo are recognized. Other packagings are described in `nonSelectedPackagingNote` and are not used for price or stock.
4. Order quantity is the BOM quantity, raised to the minimum order quantity when that minimum is higher, then rounded up to the order multiple. A DigiKey cut-tape offer does not inherit the full-reel standard package as its multiple.
5. The unit price is the highest break at or below that order quantity, in the requested currency. A cheaper 1-piece break is stored as `onePieceUnitPrice` and is used only when the order really falls on that break (`usedOnePiecePrice`). A higher tier is never used. Another currency is a mismatch, not a conversion. When DigiKey returns My Pricing, that list wins over standard pricing.
6. `canFulfill` is distributor stock versus the order quantity. Factory stock and quantity on order are reported and are not added to available stock.
7. Lead time comes from the selected packaging. `84 Days` becomes 12 weeks. Lifecycle maps NRND, EOL, obsolete, last-time-buy, and DigiKey "Not For New Designs". The worst status wins. Conflicting sources lower the confidence and keep coverage at `partial`.

`mayBeCached` is true only when the supplier response `Age` is greater than zero or `X-Cache` contains `hit`. DigiKey is called with Product Details, not keyword search. DigiKey documents that keyword results can be cached for up to 24 hours, so keyword search is not used. Demo rows are never cached quotes.

Coverage is `complete` only when identity, packaging, currency, price, stock, lead time, and lifecycle are all present on a first-party offer and the manufacturer was supplied. A matched part with a gap is `partial`. No exact part is `empty`. An HTTP or credential failure is `blocked`. A supplier this Actor does not call is `unsupported`.

`equivalentSubstitute` is always false.

### Output example

This is the Mouser row for U1 from the bundled demo, including the shortage against the bundled baseline. The same keys are present on every row. Missing values are `null`.

```json
{
  "packagingConstraint": "Tape & Reel",
  "packagingSelected": "Tape & Reel",
  "supplierPartNumber": "595-LM358DR",
  "manufacturerMatched": "Texas Instruments",
  "mpnMatched": "LM358DR",
  "description": "IC OPAMP GP 2 CIRCUIT 8SOIC",
  "sourceUrl": "https://www.mouser.com/ProductDetail/Texas-Instruments/LM358DR",
  "datasheetUrl": "https://www.mouser.com/datasheet/demo/LM358DR",
  "rohsStatus": "RoHS Compliant",
  "evidenceHash": "e46e71ed6bb4041d2d7a4fa4ae08e957b5117468ed0fa8870adbd09ae44181a0",
  "evidenceSnippet": "{\"currency\":\"USD\",\"leadTime\":\"16 Weeks\",\"lifecycle\":\"active\",\"manufacturer\":\"Texas Instruments\",\"mpn\":\"LM358DR\",\"orderQuantity\":2000,\"packaging\":\"Tape & Reel\",\"quantityAvailable\":800,\"sourceUrl\":\"https://www.mouser.com/ProductDetail/Texas-Instruments/LM358DR\",\"supplier\":\"mouser\",\"supplierPartNumber\":\"595-LM358DR\",\"unitPrice\":\"0.15\"}",
  "errorMessage": null,
  "priceSource": "standard_pricing",
  "orderMultipleSource": "supplier_multiple",
  "leadTimeRaw": "16 Weeks",
  "lifecycle": "active",
  "lifecycleRaw": "Active",
  "identityNote": null,
  "nonSelectedPackagingNote": null,
  "observedPackagings": null,
  "sourceCurrency": "USD",
  "previousLifecycle": "active",
  "previousObservedAt": "2026-09-15T12:00:00Z",
  "previousUnitPrice": "0.15",
  "unitPrice": "0.15",
  "extendedPrice": "300.00",
  "onePieceUnitPrice": "0.48",
  "nonSelectedMinUnitPrice": null,
  "digiReelFee": null,
  "priceBreaksJson": "[{\"quantity\":1,\"unitPrice\":\"0.48\",\"currency\":\"USD\"},{\"quantity\":10,\"unitPrice\":\"0.36\",\"currency\":\"USD\"},{\"quantity\":100,\"unitPrice\":\"0.22\",\"currency\":\"USD\"},{\"quantity\":1000,\"unitPrice\":\"0.15\",\"currency\":\"USD\"},{\"quantity\":2500,\"unitPrice\":\"0.12\",\"currency\":\"USD\"}]",
  "orderQuantity": 2000,
  "quantityAvailable": 800,
  "stockVersusRequired": -1200,
  "stockVersusOrder": -1200,
  "moq": 1,
  "orderMultiple": 1,
  "standardPackage": null,
  "applicableBreakQuantity": 1000,
  "factoryStock": 12000,
  "quantityOnOrder": 5000,
  "httpStatus": 200,
  "leadTimeDays": 112,
  "leadTimeWeeks": 16,
  "previousLeadTimeWeeks": 16,
  "canFulfill": false,
  "previousCanFulfill": true,
  "observationId": "97b4fd8d4b98f1cb93a79f655a928b47",
  "runMode": "demo",
  "supplier": "mouser",
  "bomLineId": "U1",
  "lineIndex": 1,
  "manufacturer": "Texas Instruments",
  "mpn": "LM358DR",
  "requiredQuantity": 2000,
  "requestedCurrency": "USD",
  "coverage": "complete",
  "confidence": "high",
  "observedAt": "2026-10-23T04:00:00Z",
  "substituteNote": "No other part is treated as electrically equivalent. A different package, a near-miss MPN, or a replacement mentioned by the supplier is not a recommendation.",
  "affiliation": "Unofficial. Not affiliated with Mouser, DigiKey, or the part manufacturers.",
  "baselineSource": "bundled_demo_baseline",
  "stateKey": "d-a949bf71a2813cd7808c2b12e2e371462ddb89238c76499824ff78e80b342c0b",
  "stateIdentity": "demo|mouser|texas instruments|LM358DR|tape_reel|USD",
  "events": "shortage",
  "dataSource": "bundled-demo-fixture",
  "demoNotice": "DEMO DATA. Computed from a bundled sample response set, not from a live Mouser or DigiKey call. demo=true. Part-supplier observations are not charged in demo mode.",
  "fulfillmentReason": "stock_short",
  "lifecycleSource": "supplier",
  "cacheNote": "Bundled demo fixture. Not a live supplier response, and not a quote.",
  "rejectedNonExactCount": 0,
  "manufacturerMismatchCount": 0,
  "candidateCount": 1,
  "demo": true,
  "materialChange": true,
  "charged": false,
  "equivalentSubstitute": false,
  "statePersisted": true,
  "eventShortage": true,
  "eventRecovery": false,
  "eventLeadTimeChange": false,
  "eventLifecycleChange": false,
  "eventPriceChange": false,
  "eventFirstObserved": false,
  "eventCheckFailed": false,
  "identityMatched": true,
  "packagingMatched": true,
  "usedOnePiecePrice": false,
  "currencyMatchesRequest": true,
  "marketplace": false,
  "moqRaised": false,
  "multipleRounded": false,
  "deliveredToDataset": true,
  "mayBeCached": false,
  "sourceReportedAReplacement": false
}
```

The run's default key-value record `OUTPUT` for that same demo:

```json
{
  "actor": "bom-part-availability-monitor",
  "title": "BOM Part Availability & Lead-Time Monitor",
  "version": "1.0.0",
  "demo": true,
  "runMode": "demo",
  "finishedAt": "2026-10-23T04:00:00Z",
  "bomLineCount": 6,
  "invalidLineCount": 0,
  "duplicateLinesSkipped": 0,
  "truncatedLineCount": 0,
  "observationCount": 12,
  "datasetRecordCount": 12,
  "chargedUnits": 0,
  "chargeEvent": "part-supplier-observed",
  "suggestedUnitPriceUsd": "0.01",
  "stoppedReason": null,
  "notDeliveredBecauseChargeLimit": 0,
  "coverage": {
    "complete": 9,
    "partial": 1,
    "empty": 2,
    "blocked": 0,
    "unsupported": 0
  },
  "events": {
    "shortage": 3,
    "recovery": 1,
    "lead_time_change": 1,
    "lifecycle_change": 1,
    "price_change": 0,
    "first_observed": 0,
    "check_failed": 0,
    "input_rejected": 0
  },
  "canFulfillCount": 8,
  "cannotFulfillCount": 4,
  "unknownFulfillmentCount": 0,
  "suppliers": [
    "mouser",
    "digikey"
  ],
  "stateStoreName": "bom-part-availability-state",
  "stateKeysWritten": 12,
  "stateKeysPreserved": 0,
  "stateUnreadableCount": 0,
  "alertOnlyOnChange": false,
  "demoNotice": "DEMO DATA. Computed from a bundled sample response set, not from a live Mouser or DigiKey call. demo=true. Part-supplier observations are not charged in demo mode.",
  "substitutePolicy": "No other part is treated as electrically equivalent. A different package, a near-miss MPN, or a replacement mentioned by the supplier is not a recommendation.",
  "affiliation": "Unofficial. Not affiliated with Mouser, DigiKey, or the part manufacturers.",
  "partialDueToChargeLimit": false
}
```

`LEDGER` holds every observation from the run. In alert-only mode the dataset is the subset where `materialChange` is true, and the ledger still has the quiet checks.

### Events and the baseline

`events` is a pipe-separated list, in this order:

| Event | When it is set |
| --- | --- |
| `shortage` | The part could be fulfilled and now cannot, or this is the first observation and it cannot. |
| `recovery` | It could not be fulfilled and now can. |
| `lead_time_change` | Both lead times are numeric and differ by at least one week. |
| `lifecycle_change` | Both lifecycles are known and differ. `unknown` does not count. |
| `price_change` | Same currency and the unit price moved by at least 0.5%. |
| `first_observed` | No baseline existed yet. |
| `check_failed` | The call was blocked, or the saved baseline could not be read. No shortage is inferred from a missing baseline. |
| `input_rejected` | The line failed validation, or the supplier is unsupported. |

Only `complete`, `partial`, and `empty` update the named state store, and only after a live charge succeeds (demo writes without charging). `blocked` and `unsupported` leave the previous snapshot in place. A run that ends in an exception does not mark `last-successful-run`. A run that stops because the charge limit was reached is a partial success: the observations already charged stay as the new baseline, and the parts not reached keep the old one.

State keys are separate for demo and live, so a demo schedule cannot overwrite a live baseline.

### Pricing

This Actor uses pay-per-event pricing. There is no per-dataset-item charge, so a row is never billed twice.

| Event | Price | When it is charged |
| --- | --- | --- |
| `apify-actor-start` | $0.005 | Every run, including demo. This is the platform start event. |
| `part-supplier-observed` | $0.01 | One live observation whose coverage is `complete`, `partial`, or `empty`. |

Demo runs do not charge `part-supplier-observed`. Blocked, unsupported, and rejected lines are not charged. A repeated observation id in the same run is not charged twice. Alert-only mode still charges each successful live check, because the check is the product. Quiet rows are in `LEDGER`.

Worked examples:

| Run | Arithmetic | Total |
| --- | --- | --- |
| Demo daily test, no keys | Start event only | $0.005 |
| Pilot: 25 parts, both suppliers, one live run | 50 × $0.01 + $0.005 | $0.505 |
| Daily watch: 50 parts, both suppliers, 30 days | 3,000 × $0.01 + 30 × $0.005 | $30.15 |

A maximum charge stops the run cleanly. After the start fee, each further $0.01 pays for one observation. The Actor writes an observation only when the charge succeeds, then stops if that charge reports the limit. An observation the platform refuses is not written and does not replace its baseline. `OUTPUT.notDeliveredBecauseChargeLimit` counts the parts left unchecked, and `partialDueToChargeLimit` is true. On a $5 cap, the start fee leaves about $4.995, which pays for 499 observations at $0.01 ($4.99). What happens to the next $0.01 depends on the platform's remaining-budget rule. If that charge is accepted and flagged as the limit, the row is delivered and the run stops. If it is refused, that row is omitted and the run stops. Either way the earlier rows keep their new baseline.

Your Mouser and DigiKey quotas are yours. This price does not include their fees.

### Scope and limits

- Official HTTP APIs only. Mouser Search API `partnumber` with the exact option, batched up to 10. DigiKey Product Information v4 Product Details, one MPN at a time, after a client-credentials token. Hosts are `api.mouser.com`, `api.digikey.com`, and `sandbox-api.digikey.com`. Redirects are not followed. Calls carry a User-Agent that identifies this Actor and the contact address below.
- Calls are throttled and retried on 429 and 5xx with backoff. Secrets are redacted from error text.
- The customer must comply with the Mouser and DigiKey API terms. Do not use the output to build a public parts database. The data belongs to the response you were authorized to request.
- No ordering, shopping cart, AVL, date-code, or contract-price file. A Digi-Reel fee is reported and is not added into `extendedPrice`.
- Empty results are real answers and are charged. They can turn a previously fulfillable part into a shortage.
- Marketplace offers are used only when no first-party offer matched, and then coverage stays partial.
- The sample catalog is six parts. A demo MPN outside that catalog is `empty` with reason `not_in_demo_catalog` and no invented price.
- Default memory is 512 MB, with a floor of 256 MB and a ceiling of 1024 MB. A few hundred lines finish well inside a one-hour run timeout.

### Integrations

Schedule the Actor daily in Apify. Point a webhook, n8n, or Make scenario at the dataset or at the `OUTPUT` key.

- Filter rows where `events` contains `shortage`, `recovery`, `lead_time_change`, or `lifecycle_change`.
- Use `alertOnlyOnChange` when the webhook should stay quiet, and read `LEDGER` when you want the full check.
- Send the flat dataset to Google Sheets. Prices are decimal strings (`0.15`, `0.0082`) so a spreadsheet does not have to guess. Text that could look like a formula is prefixed with a quote.
- Join back to your BOM on `bomLineId` or on manufacturer + MPN + packaging + currency.
- Keep a separate `stateStoreName` per customer or per BOM.

### Run it locally

Python 3.12.

```bash
pip install -r requirements.txt
pip install pytest pytest-asyncio
pytest
mkdir -p storage/key_value_stores/default
echo '{}' > storage/key_value_stores/default/INPUT.json
APIFY_LOCAL_STORAGE_DIR="$PWD/storage" python -m src
```

The empty input prints a demo summary and writes `storage/datasets/default/`. `chargedUnits` in `OUTPUT` is 0.

Questions: plusonecares@gmail.com.

### FAQ

**Why is the unit price higher than the 1-piece price on the same page?**
The 1-piece break does not apply to a build that must order thousands. `unitPrice` is the break at the order quantity. `onePieceUnitPrice` is there so you can see what was not used.

**Will it suggest a drop-in replacement when the part is short?**
No. A different suffix, a different package, and a replacement named by the supplier are reported as not used. `equivalentSubstitute` stays false. You decide whether any other part is acceptable.

**Does alert-only mode make unchanged days free?**
No. In live mode every successful observation is charged once. Alert-only mode only changes which rows are copied into the dataset.

**A supplier returned 401. Did it wipe yesterday's good stock figure?**
No. Blocked and unsupported results are not charged and do not replace the baseline. The row explains the failure. If every live row is blocked, the run fails after writing those explanations, and `last-successful-run` is left as it was.

**Can I price EUR from a USD break?**
No. The line's currency is sent to DigiKey as the currency header, and Mouser prices in another currency are a mismatch. Nothing is converted.

**Whose API quota is this?**
Yours. The keys stay in secret inputs and are sent only to the official hosts.

# Actor input Schema

## `runMode` (type: `string`):

Auto runs the labelled demo when no selected supplier has credentials, and calls the official APIs when at least one selected supplier is configured. Demo never sends credentials and never charges part-supplier observations. Live requires your keys and never invents prices.

## `suppliers` (type: `array`):

Official APIs to ask. Mouser uses the Search API. DigiKey uses Product Details, not keyword search. Any other name is returned as unsupported. This Actor does not scrape websites.

## `bomJson` (type: `string`):

Inline JSON array. Each line needs manufacturer, mpn, quantity; optional packaging, currency, lineId. Example: \[{"lineId":"U1","manufacturer":"Texas Instruments","mpn":"LM358DR","quantity":2000,"packaging":"Tape & Reel"}]. Used before CSV and URL. Leave empty in demo mode.

## `bomCsv` (type: `string`):

Inline CSV with a header row. Recognized columns: manufacturer, mpn, quantity, packaging, currency, lineId. Used when BOM JSON is empty.

## `bomUrl` (type: `string`):

Public https URL of a JSON or CSV BOM. Private addresses, localhost, and link-local hosts are rejected. Used when JSON and CSV are empty.

## `currency` (type: `string`):

Used when a line does not name a currency. Prices in another currency are reported as a mismatch and are not converted.

## `alertOnlyOnChange` (type: `boolean`):

When true, the dataset contains only material changes and failed checks. Every successful observation is still charged in live mode and is stored in the LEDGER key of the run's key-value store. Quiet checks are the product, not a free extra.

## `stateStoreName` (type: `string`):

Named key-value store for the baseline. Use one name per BOM or customer so schedules do not overwrite each other. A blocked or failed check does not replace a saved baseline.

## `maxBomLines` (type: `integer`):

Lines after this cap are counted as truncated and are not checked.

## `minRequestIntervalMs` (type: `integer`):

Pause between official API calls. Retries use additional backoff.

## `mouserApiKey` (type: `string`):

Your Mouser Search API key. Optional. Stored as a secret and sent only to api.mouser.com. Unofficial integration, not affiliated with Mouser.

## `digikeyClientId` (type: `string`):

Client ID from your DigiKey API app subscribed to Product Information. Optional secret. Unofficial integration, not affiliated with DigiKey.

## `digikeyClientSecret` (type: `string`):

Client secret for the same DigiKey app. Optional secret. Used only to request a client-credentials token from DigiKey.

## `digikeyAccountId` (type: `string`):

Optional. Sent as X-DIGIKEY-Account-Id when your DigiKey app requires an account id for 2-legged pricing.

## `digikeySandbox` (type: `boolean`):

Call sandbox-api.digikey.com instead of api.digikey.com. Leave off for production keys.

## `digikeyLocaleSite` (type: `string`):

Optional 2-letter DigiKey site, such as US, DE, or UK. When empty, the site is chosen from the line currency. The currency header is still the line currency, and prices are never converted.

## Actor input object example

```json
{
  "runMode": "auto",
  "suppliers": [
    "mouser",
    "digikey"
  ],
  "currency": "USD",
  "alertOnlyOnChange": false,
  "stateStoreName": "bom-part-availability-state",
  "maxBomLines": 200,
  "minRequestIntervalMs": 250,
  "digikeySandbox": false
}
```

# Actor output Schema

## `observations` (type: `string`):

Dataset of flat records. In alert-only mode this contains material changes and failed checks.

## `summary` (type: `string`):

Counts, coverage, charged units, and whether the run stopped at the charge limit.

## `ledger` (type: `string`):

Every observation from the run, including unchanged checks omitted from the dataset when alert-only mode is on.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("plusonets/bom-part-availability-monitor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("plusonets/bom-part-availability-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call plusonets/bom-part-availability-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,plusonets/bom-part-availability-monitor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/J1Nh66Vg0pQLKRPM1/builds/VJAFFT5hejkVUVxgr/openapi.json
