# SKUFuse — Inventory Sync Guard (`overbifrost/skufuse-inventory-sync-guard`) Actor

Compare current and proposed inventory CSV snapshots before synchronization. Flags large stock changes, stock going to zero, and SKUs added or removed. Comparison only

- **URL**: https://apify.com/overbifrost/skufuse-inventory-sync-guard.md
- **Developed by:** [Lasse Bjerkholt Olsen](https://apify.com/overbifrost) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $500.00 / 1,000 comparison completeds

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

## SKUFuse — Inventory Sync Guard

SKUFuse compares a **current inventory CSV snapshot** with a **proposed
inventory CSV snapshot** and reports every change that needs human review
**before** you synchronize it — stock drops or increases beyond your
thresholds, stock going to zero, SKUs added or removed, duplicate SKUs, and
rows it could not check.

**What it is not.** SKUFuse never synchronizes anything. It does not connect
to an ERP, PIM, WMS, marketplace, Shopify, WooCommerce, or any other live
system. It does not update or reserve stock, repair files, forecast demand, or
infer live availability. A clean report (`no_findings`) means *no review items
were found* — it is not a guarantee that a synchronization is safe.

### Quick start (Apify Console)

1. Open the Actor in Apify Console and go to the **Input** tab.
2. Leave **Run synthetic demo** turned **on** and press **Start** — SKUFuse
   compares its built-in DEMO-\* data and you immediately see what a report
   looks like. No files needed.
3. For your real snapshots, turn **Run synthetic demo** **off**, then:
   - **Upload** your current inventory CSV and your proposed snapshot CSV
     under **Files** — straight from your computer, up to 10 MB each.
   - Actively check the complete-snapshots declaration.
4. Press **Start**, then open **Review report (HTML)** in the run's output —
   a readable page with the status, the counts that matter, and the findings.
   The canonical report and review sheet are also in storage:
   - **Key-value store** — `REVIEW.html` (the readable report), `OUTPUT`
     (canonical JSON report), `FINDINGS.csv` (spreadsheet-ready review sheet),
     `RUN_METADATA` (operational/billing metadata).
   - **Dataset** — exactly one item: the same JSON report.

Pasting CSV text or pointing to a public HTTPS URL works too — those fields
live under **Advanced** and are meant for API use and automation. Each file
still needs exactly one source: upload, paste, or URL.

### Synthetic example input

The demonstration mode exists so you can evaluate the output before preparing
exports — and it is what the platform's automated health check runs. It
compares a fixed, built-in synthetic pair and nothing else:

```json
{ "demoMode": true }
```

An entirely empty input object `{}` selects the same demonstration — that is
the input a bare default-input run produces. Every other option (`rules`,
`maxFindings`) is honored exactly as in a real run when supplied.

**The demonstration never mixes with your data.** `demoMode: true` combined
with any source field (`currentCsvFile`, `currentCsv`, `currentCsvUrl`,
`proposedCsvFile`, `proposedCsv`, `proposedCsvUrl`) fails `INPUT_INVALID`.
Reports from it are marked
`syntheticDemo: true` so they can never be mistaken for a real snapshot
comparison. A demo run is billed like any other run (3 evaluated rows —
see [Pricing](#pricing-pay-per-event)).

The built-in pair: `DEMO-COF-001` drops `12 → 0`; `DEMO-MUG-002` is unchanged;
`DEMO-TEE-003` is added at stock `8`.

#### Expected demo output

`status: "review_required"`, `syntheticDemo: true`, `evaluatedRows: 3`,
`addedRows: 1`, and three findings: `STOCK_DROP_EXCEEDS_THRESHOLD` and
`STOCK_BECOMES_ZERO` on `DEMO-COF-001` (`12 → 0` vs. the default 50 % drop
threshold), plus `SKU_ADDED` on `DEMO-TEE-003`.

### Pricing (pay-per-event)

| Event | Price | Units charged |
|---|---|---|
| `comparison-completed` | **$0.50** | 1 if `evaluatedRows > 0`, else 0 |
| `row-evaluated` | **$0.00025** | `evaluatedRows` |
| `apify-actor-start` | **$0.00005** | 1 per run — platform-managed, always applies |

Charged **only after** all report outputs are delivered. A 3-row demonstration
run costs $0.50080; a 1,000-row comparison costs $0.75005.

`apify-actor-start` is charged automatically once per run at the configured
memory (≤1 GiB) before any Actor code runs. It is a platform charge, not a
SKUFuse custom event: the Actor never calls `charge()` for it, it never appears
in `RUN_METADATA.billing.expected`/`confirmed`, and it applies on **every**
pay-per-event run — including zero-custom-charge outcomes like header-only,
invalid-input, or insufficient-budget runs. `apify-default-dataset-item` and
any other paid synthetic event are not permitted.

`evaluatedRows` counts every deterministically classified proposed row: matched
pairs **and** valid additions. Not billable: removed SKUs (current-only rows),
uncheckable rows, header-only files, and insufficient-budget runs. An unchanged
evaluated row is billable; a row with several findings is still one row event.

Before charging, the run checks the **combined** cost (the platform start fee

- base + all row events) against the run's budget cap with exact decimal
  arithmetic — the start fee is inside the same `maxTotalChargeUsd` cap. If the
  whole report does not fit, it fails `BUDGET_INSUFFICIENT` — no report, no
  partial custom charges, no billed prefix. Effective prices come from the live
  run's pricing info, never hardcoded.

Local runs (`billing.mode: local`) and unconfigured private Actors
(`unconfigured`) never charge (`billing.outcome: not_applicable`).

#### Failed runs and confirmed charges

Charging is the last step, so a run can fail *after* one or more charges were
already confirmed. A failed run is **not** automatically non-billable — the
billing outcome is a separate fact recorded in `RUN_METADATA.billing.outcome`:

| Outcome | Meaning |
|---|---|
| `blocked` | Nothing was attempted or confirmed — e.g. the run failed before charging began. Not billable. |
| `completed` | Every expected unit was confirmed — even if the run then failed during final bookkeeping. The charge stands. |
| `partial` | Some units were confirmed but charging did not complete — the confirmed units stand. |
| `unknown` | A charge's result could not be confirmed. Uncertain is **not** zero; the Actor never retries an uncertain charge. |
| `not_required` | Pay-per-event run with zero evaluated rows — no custom units due (the platform's `apify-actor-start` fee still applies). |
| `not_applicable` | Local or unconfigured run — charging never ran. |
| `pending` | Transient — a run killed before final bookkeeping may leave this in the last persisted metadata; it is not a settled outcome. |

Report outputs already delivered (`OUTPUT`, `FINDINGS.csv`, `REVIEW.html`, the dataset item)
stay in the run's storage whatever the outcome. The exact expected and
confirmed unit counts per event are in `RUN_METADATA.billing.expected` and
`RUN_METADATA.billing.confirmed`; the platform-side charge record is in Apify
Console under the run's usage/charges.

There is no all-or-nothing charging, no automatic refund, and no exactly-once
guarantee: a confirmed charge is not undone by a later failure, and each new
run is a new billable comparison.

### Limitations

- **One fixed CSV profile** (`inventory_snapshot_single_location_v1`):
  `SKU` + `Stock` columns, one location, one snapshot pair. Multi-location
  inventory, pricing, and other CSV dialects are out of scope.
- **Comparison only.** No synchronization, no live-system connection, no
  repair, no forecasting. `liveInventoryVerified` and
  `syncSafetyGuaranteed` are always `false` in the report — by design.
- Exact, case-sensitive SKU matching; duplicates are never resolved — every
  duplicate makes its SKU uncheckable.
- **Additions and removals are only meaningful on complete snapshots.** The
  `confirmCompleteSnapshots` declaration is your explicit assertion that both
  files cover the same complete inventory scope — without it the run refuses
  to interpret absence as an addition or removal.
- Hard ceilings below; exceeding them fails the run with a stable error code —
  source data is never silently truncated.

| Resource | Ceiling |
|---|---|
| Whole input object | 2 MiB |
| Each inline CSV | 512 KiB |
| Each remote CSV | 10 MiB streamed (Content-Length is not trusted) |
| Data records per file | 100,000 |
| Columns per file | 128 |
| Retained findings | `maxFindings`, hard cap 1000 |
| Source URL | HTTPS only, port 443, ≤ 4096 bytes |
| Remote retrieval | 30 s total (DNS 5 s, connect/TLS 10 s, idle 10 s) |
| Whole run | 100 s application deadline |

### Privacy and retention

- The demonstration mode sends **no inventory data anywhere** — it compares
  built-in DEMO-\* rows only, and `demoMode: true` combined with any source
  field is rejected.
- Apify retains the run's **INPUT** record under the Actor's storage and
  retention settings. Input may contain complete inline snapshots
  (`currentCsv`/`proposedCsv`) or source URLs — including signed URLs with
  embedded credentials. Prefer pre-signed URLs with short expiry, or inline
  CSVs you are comfortable retaining, and clean up run storage when done.
- The report echoes inventory content — that is the product. It stays in your
  run's storage under Apify's normal storage retention rules.
- `ERROR`, `RUN_METADATA`, `RUN_STATE`, and log output contain **no** URLs,
  SKUs, cell values, secrets, or raw exception text. Input validation errors
  point at a sanitized location (a field name for supported fields, a fixed
  marker for unsupported ones) — never the offending property name verbatim.
- No analytics, no telemetry, no cross-run stores, no external services beyond
  your two declared source URLs.
- Re-running the Actor in a used store fails `RESUME_UNSUPPORTED` — a run
  cannot silently append to or re-charge an earlier run's evidence.

### Running a real comparison

#### Prepare the two CSVs

Produce one current snapshot and one proposed snapshot of the **same complete
inventory scope** — the set of SKUs and on-hand quantities you are about to
synchronize. Fixed profile `inventory_snapshot_single_location_v1`:

- Required columns: **`SKU`, `Stock`** — exact names, case-sensitive,
  order-insensitive; extra columns are ignored and reported under
  `ignoredColumns`.
- **SKU** — exact, case-sensitive matching; up to 256 UTF-8 bytes; leading
  zeroes, Unicode variants, and lookalike characters stay distinct; boundary
  whitespace is rejected, not trimmed.
- **Stock** — a signed base-10 integer string with an optional leading minus
  and ASCII digits only (`-2147483648`–`2147483647`). Plus signs, decimals,
  exponents, thousands separators, surrounding whitespace, empty cells, and
  status text like `instock` are all rejected.
- Every data row is validated — there is no ignored-row convention in this
  profile. A row that fails validation is a finding, never silently skipped.
- Encoding: UTF-8 only; one leading BOM tolerated; NUL rejected; LF/CRLF/CR
  endings all accepted.

#### The complete-snapshots declaration — actively confirmed, never pre-filled

`confirmCompleteSnapshots` has no default or prefill anywhere: you must set it
to `true` yourself. It asserts that both files represent the **same complete
inventory scope** — only then is a missing SKU a genuine addition or removal
rather than a scope mismatch. Real comparisons fail
`COMPLETE_SNAPSHOTS_UNCONFIRMED` without it. It is not required for `demoMode`
— no caller files exist for it to describe.

#### Input contract

One JSON object. Unknown properties, nulls, wrong types, and unsupported
options are rejected — nothing is coerced or ignored silently.

| Field | Required | Meaning |
|---|---|---|
| `demoMode` | no | `true` (or an empty input object) selects the built-in synthetic demonstration. Forbidden alongside any source field. |
| `schemaVersion` | real comparisons | Must be exactly `inventory_snapshot_single_location_v1`; also accepted (and validated) in demo mode |
| `confirmCompleteSnapshots` | real comparisons | Must be `true`: you assert both snapshots cover the same complete inventory scope |
| `currentCsvFile` / `currentCsv` / `currentCsvUrl` | real comparisons, one of them | Current snapshot — file upload, inline text, or HTTPS URL — never more than one, never none, never in demo mode |
| `proposedCsvFile` / `proposedCsv` / `proposedCsvUrl` | real comparisons, one of them | Proposed snapshot, same rules |
| `rules.maxStockDropPercent` | no, default `50` | 0–10000, step 0.01. Flagged when the decrease **strictly exceeds** it |
| `rules.maxStockIncreasePercent` | no, default `200` | 0–10000, step 0.01. Same rule for increases |
| `rules.flagStockToZero` | no, default `true` | Flag stock changing positive → 0 |
| `rules.flagAddedSku` | no, default `true` | Flag SKUs present only in the proposed snapshot |
| `rules.flagRemovedSku` | no, default `true` | Flag SKUs present only in the current snapshot |
| `maxFindings` | no, default `1000` | 1–1000. Bounds the returned finding list only — every finding is still counted |

Mixed modes are allowed (e.g. current uploaded + proposed inline, or current
inline + proposed via URL). `currentCsvFile`/`proposedCsvFile` take the
reference the Apify `fileupload` editor produces — an
`https://api.apify.com/v2/key-value-stores/{storeId}/records/{key}` URL.
Uploads are fetched from that key-value store record, capped at 10 MiB, and
validated byte-for-byte like every other source; any other URL shape is
rejected as `INPUT_INVALID`.

#### The drop-to-zero example

Current `A100` at stock `12`, proposed at `0` — a 100 % decrease. With the
default 50 % drop threshold the report returns `status: "review_required"`
with two findings, in this order:

- `STOCK_DROP_EXCEEDS_THRESHOLD` — before `12`, after `0`,
  `changePercent: "-100.0000"`, threshold `50`
- `STOCK_BECOMES_ZERO` — stock `12 → 0`

Percentage rules run only from a **positive** current baseline — a zero or
negative current stock has no meaningful percentage, so no percentage finding
is ever invented from it.

### Output

#### `REVIEW.html` (human-readable review report)

A self-contained HTML page generated from the canonical report: status
headline, the counts that drive a decision (evaluated / added / removed /
uncheckable rows, findings), scope and truncation notices, and the retained
findings in readable form. It carries no scripts or external assets and is
safe to open anywhere. `REVIEW.html` is a convenience presentation — `OUTPUT`
remains the canonical, machine-readable report.

#### `OUTPUT` (JSON report, also the single dataset item)

| Field | Meaning |
|---|---|
| `status` | `no_findings` · `review_required` · `incomplete` · `no_changes` |
| `scope` | Always `comparison_only` |
| `liveInventoryVerified` / `syncSafetyGuaranteed` | Always `false` — by design |
| `syntheticDemo` | `true` only for the built-in synthetic demonstration — never for a real snapshot comparison |
| `rules`, `maxFindings` | The normalized options the report was produced under |
| `currentRows`, `proposedRows` | Data-record counts per file |
| `evaluatedRows` | Proposed rows deterministically classified — matched pairs plus valid additions |
| `addedRows` | Valid unique proposed SKUs genuinely absent from current |
| `removedRows` | Valid unique current SKUs genuinely absent from proposed |
| `currentUncheckableRows` | Defective current rows excluded from matching |
| `proposedUncheckableRows` | Proposed rows that could not be evaluated or classified |
| `coverageComplete` | True only when proposed has rows, every proposed row was evaluated, and `currentUncheckableRows` is zero |
| `findingsTotal` / `findingsReturned` | All findings vs. the bounded list returned |
| `truncated` | True exactly when `findingsTotal > findingsReturned` |
| `findingsByCode` | Complete counts per code (zero counts omitted) |
| `ignoredColumns` | Ignored extra columns per file |
| `findings` | Bounded list, deterministic order |

Status priority: **`incomplete`** (any uncheckable row or truncation) →
**`no_changes`** (both files empty of data rows) → **`review_required`**
(findings or classified additions/removals) → **`no_findings`**.

Finding order: current-file row diagnostics in source order, then
proposed-file findings in source order, then removal findings in current
source order.

#### Finding codes

| Code | Severity | Meaning |
|---|---|---|
| `MISSING_SKU`, `INVALID_SKU` | warning | SKU cell empty or invalid |
| `DUPLICATE_SKU` | warning | SKU appears more than once in a file — never resolved to a winner |
| `INVALID_STOCK` | warning | Stock cell fails the signed-integer grammar |
| `SKU_ADDED` | review | Proposed SKU genuinely absent from the current snapshot |
| `SKU_REMOVED` | review | Current SKU genuinely absent from the proposed snapshot |
| `CURRENT_SKU_AMBIGUOUS` | uncheckable | Proposed SKU matches duplicated current SKUs |
| `CURRENT_ROW_UNCHECKABLE` | uncheckable | The matched current row is defective |
| `STOCK_DROP_EXCEEDS_THRESHOLD` | warning | Decrease strictly above the drop threshold |
| `STOCK_INCREASE_EXCEEDS_THRESHOLD` | warning | Increase strictly above the increase threshold |
| `STOCK_BECOMES_ZERO` | warning | Stock positive → 0 |

Each finding carries `code`, `severity`, `source`, `row`, `line`, `sku`,
`field`, `message` — plus code-specific `before`/`after`/`changePercent`,
`currentRow`/`currentLine`, or a bounded `rawValue` excerpt (max 128 code
points). `field` names are the profile column names (`SKU`, `Stock`). Findings
never contain full rows, URLs, or raw exception text.

#### `FINDINGS.csv`

Same findings as a review spreadsheet — fixed columns, UTF-8 without BOM,
quoted as needed, formula-injection protected (cells starting with
`= + - @` or tab/CR/LF, ignoring leading spaces, get a `'` prefix).

#### `RUN_METADATA` / `RUN_STATE` / `ERROR`

Operational records: run state, `reportAvailable`, billing
mode/outcome/expected/confirmed counts, `errorCode`, `durationMs`. `ERROR`
records carry only a stable code, a safe message template, and structured
location fields — never URLs, SKUs, cell values, or raw exceptions.

### Remote CSV security

`currentCsvUrl`/`proposedCsvUrl` are deliberately constrained:

- HTTPS only, hostname required, port omitted or 443, no credentials in the
  URL, no fragment, no IP literals, no single-label hosts, no IPv6
- DNS must resolve to **public IPv4** only — every private/reserved/special
  range is blocked, including rebinding tricks and non-canonical IPv4 forms
- The connection is pinned to the approved DNS answer and the connected peer
  is re-verified — DNS pinning, not just DNS checking
- No redirects, no compression (`identity` encoding only)
- Actual streamed bytes capped at 10 MiB regardless of headers
- Deadlines: DNS 5 s, connect 10 s, idle 10 s, total 30 s

If your file is behind auth, download it yourself and use the inline CSV
fields — there are no credential or custom-header options by design.

### Using the API / SDK

#### REST

```bash
## Start a run
curl -X POST \
  "https://api.apify.com/v2/acts/YOUR_ACTOR_ID/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d @input.json

## Poll until SUCCEEDED, then read outputs
curl "https://api.apify.com/v2/key-value-stores/STORE_ID/records/OUTPUT?token=$APIFY_TOKEN"
curl "https://api.apify.com/v2/datasets/DATASET_ID/items?token=$APIFY_TOKEN"
```

#### JavaScript SDK

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('YOUR_USER/skufuse-inventory-sync-guard').call(input);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const report = items[0];              // the full report — exactly one item
const csv = await client.keyValueStore(run.defaultKeyValueStoreId)
  .getRecord('FINDINGS.csv');
```

#### Make / n8n (plain HTTP)

1. **HTTP → Make a request**: `POST`
   `https://api.apify.com/v2/acts/YOUR_ACTOR_ID/run-sync-get-dataset-items?token=…`
   with the input JSON — returns the dataset items directly when the run
   finishes (sync endpoint, simplest for automations).
2. Or async: `POST …/runs` → poll `GET …/runs/{runId}` until
   `status: SUCCEEDED` → `GET …/datasets/{defaultDatasetId}/items`.
3. On `FAILED`, fetch `…/key-value-stores/{storeId}/records/ERROR` for the
   stable `error.code`.

### Error codes

Fatal errors fail the run with a stable code (also in the `ERROR` record):

`INPUT_INVALID`, `INPUT_TOO_LARGE`, `UNSUPPORTED_SCHEMA`,
`COMPLETE_SNAPSHOTS_UNCONFIRMED`,
`SOURCE_URL_INVALID`, `SOURCE_DESTINATION_BLOCKED`, `SOURCE_DNS_FAILED`,
`SOURCE_FETCH_FAILED`, `SOURCE_TIMEOUT`, `SOURCE_REDIRECT_UNSUPPORTED`,
`SOURCE_HTTP_STATUS`, `SOURCE_ENCODING_UNSUPPORTED`, `SOURCE_TOO_LARGE`,
`CSV_INVALID_ENCODING`, `CSV_FORBIDDEN_CHARACTER`, `CSV_SYNTAX_INVALID`,
`CSV_HEADER_MISSING`, `CSV_HEADER_INVALID`, `CSV_DUPLICATE_HEADER`,
`CSV_MISSING_COLUMN`, `CSV_COLUMN_COUNT`, `CSV_LIMIT_EXCEEDED`,
`BUDGET_INSUFFICIENT`, `BILLING_CONFIGURATION_INVALID`,
`BILLING_STATE_CONFLICT`, `BILLING_PARTIAL`, `BILLING_OUTCOME_UNKNOWN`,
`OUTPUT_WRITE_FAILED`, `RESUME_UNSUPPORTED`, `RUN_DEADLINE_EXCEEDED`,
`INTERNAL_ERROR`.

# Actor input Schema

## `demoMode` (type: `boolean`):

Try SKUFuse on built-in synthetic inventory data — no files needed and nothing about your stock is sent. Leave ON for a first look at the report; turn OFF to compare your own inventory CSVs. When off, real sources and the declaration below are required. A demo run is billed like any other run (one comparison over 3 rows).

## `currentCsvFile` (type: `string`):

Upload the current inventory snapshot as a CSV file from your computer (up to 10 MB). Headers: SKU, Stock. Exactly one source per file — either this upload or one of the advanced options below.

## `proposedCsvFile` (type: `string`):

Upload the proposed inventory CSV from your computer (up to 10 MB) — the file you plan to synchronize. Headers: SKU, Stock. Exactly one source per file.

## `rules` (type: `object`):

Configure the stock-change thresholds and whether added/removed SKUs and positive-to-zero stock are reported.

## `confirmCompleteSnapshots` (type: `boolean`):

Required declaration: both files cover the same complete inventory scope, so a SKU absent from one file genuinely means added or removed. Without it the Actor refuses to interpret absence. Intentionally not pre-checked.

## `currentCsv` (type: `string`):

Paste the current inventory snapshot as CSV text (max 512 KB). Headers: SKU, Stock. An alternative to the file upload above — use exactly one source per file.

## `proposedCsv` (type: `string`):

Paste the proposed inventory CSV as text (max 512 KB). Headers: SKU, Stock. Exactly one source per file.

## `currentCsvUrl` (type: `string`):

Public HTTPS URL returning the current CSV (max 10 MB). No redirects, no URL credentials, port 443 only.

## `proposedCsvUrl` (type: `string`):

Public HTTPS URL returning the proposed CSV (max 10 MB). No redirects, no URL credentials, port 443 only.

## `maxFindings` (type: `integer`):

Maximum number of findings retained in the report (1–1000). Totals are always counted completely; only the returned list is bounded.

## `schemaVersion` (type: `string`):

Fixed input contract version. Any other value fails with UNSUPPORTED\_SCHEMA.

## Actor input object example

```json
{
  "demoMode": true,
  "currentCsvUrl": "https://example.com/exports/current-inventory.csv",
  "proposedCsvUrl": "https://example.com/exports/proposed-inventory.csv",
  "maxFindings": 1000,
  "schemaVersion": "inventory_snapshot_single_location_v1"
}
```

# Actor output Schema

## `reviewHtml` (type: `string`):

No description

## `findingsCsv` (type: `string`):

No description

## `report` (type: `string`):

No description

## `datasetItem` (type: `string`):

No description

## `runMetadata` (type: `string`):

No description

## `errorRecord` (type: `string`):

No description

# 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 = {
    "demoMode": true,
    "schemaVersion": "inventory_snapshot_single_location_v1"
};

// Run the Actor and wait for it to finish
const run = await client.actor("overbifrost/skufuse-inventory-sync-guard").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 = {
    "demoMode": True,
    "schemaVersion": "inventory_snapshot_single_location_v1",
}

# Run the Actor and wait for it to finish
run = client.actor("overbifrost/skufuse-inventory-sync-guard").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 '{
  "demoMode": true,
  "schemaVersion": "inventory_snapshot_single_location_v1"
}' |
apify call overbifrost/skufuse-inventory-sync-guard --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,overbifrost/skufuse-inventory-sync-guard"
        }
    }
}
```

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/lXpy82nw4OAz88f9v/builds/maREilvbZK5Ed7sav/openapi.json
