# My Actor (`savagnic/my-actor`) Actor

- **URL**: https://apify.com/savagnic/my-actor.md
- **Developed by:** [NICHOLAS SAVAGE](https://apify.com/savagnic) (community)
- **Categories:**
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## AgentShelf — OpenAI product feed conformance validator

Give it your product feed. It tells you, row by row, **which items OpenAI's
agentic-commerce ingest would reject and exactly why** — before you ever upload
anything.

It runs entirely offline. It does not upload your feed, it does not call an
OpenAI API, and it does not need a merchant account. It reads bytes you hand it
and applies the rules from the published
[feed specification](https://developers.openai.com/commerce/specs/feed/).

### What it does

| You give it | You get back |
|---|---|
| a CSV, TSV, JSON or NDJSON feed | a conformance report: accepted vs. rejected counts, an acceptance rate, duplicate item ids, unknown and missing columns |
| the same, `normalize: true` | the above plus every accepted row projected into **OpenAI native field names**, exported as a CSV you can ship |

Both dialects are understood. Point it at an existing **Google Merchant Center**
feed and it maps the Google field names onto the OpenAI ones before checking, so
you can find out what an existing catalogue would cost you without rebuilding it
first.

Every finding carries a rule id, the field it fired on, the offending value and
the source line number, so a rejection is something you can fix rather than
something you have to guess at.

Set `failOn` to decide which findings make the run exit non-zero. The report is
stored either way, so a "failed" run is still a readable report.

### Input

Full contract in `input_schema.json`. Unknown fields are **rejected**, not
ignored — including anything that looks like a URL, because this Actor performs
no network requests of its own. Supply the feed exactly once, either inline as
`feedText` or as a record in this run's own key-value store named by `feedKey`.

| Field | Type | Default | Notes |
|---|---|---|---|
| `feedText` | string | – | the feed itself, inline |
| `feedKey` | string | – | a record name in this run's own key-value store holding the feed; a plain name, not a path |
| `format` | `auto` | `csv` | `tsv` | `json` | `ndjson` | `auto` | force a parser instead of sniffing the bytes |
| `dialect` | `auto` | `openai` | `google` | `auto` | which field-name and rule set to apply |
| `merchantName` | string | – | your registered display name, checked against the `seller_name` rule; taken on trust, not a credential |
| `adsFeed` | boolean | `false` | also apply the rules that only bind for Ads feeds |
| `checkRecommended` | boolean | `true` | also report recommended-field gaps as warnings |
| `normalize` | boolean | `false` | map to native field names and emit the accepted rows |
| `emitCsv` | boolean | `true` | write the CSV exports as key-value records |
| `failOn` | `error` | `warning` | `never` | `error` | which findings make the run exit `1` |
| `maxRows` | integer | `50000` | refuse a larger feed rather than validate a prefix of it (ceiling `500000`) |
| `tenantId` | string | – | free-text label echoed into the result for your own attribution |

### Output

One `agentshelf/actor-result/1` object per run, written **identically** to the
default dataset and to the `OUTPUT` key-value record, on success and on failure
alike. The shape is described in `dataset_schema.json` and `output_schema.json`;
a real local run's output is in [`sample-output.json`](./sample-output.json),
produced by

```bash
npm ci && npm run build
mkdir -p storage/key_value_stores/default
## the bundled Google-dialect fixture as the `feedText` field
node products/agentshelf/dist/actor/main.js
```

so you can reproduce it apart from the timestamp and the duration.

The CSV exports are **not** inlined into that JSON. They are stored as their own
key-value records, so you can download them as spreadsheets:

| Record | Written when |
|---|---|
| `rejections.csv` | a report was produced and `emitCsv` was left on — one row per finding |
| `accepted-rows.csv` | `normalize` and `emitCsv` were both on — the passing rows in native fields |

A very large feed can produce a per-row array too big for one dataset item. In
that case the summary, the rule tally and the digest are kept, the per-row array
is set to `null`, and `data.truncated` says how many rows were dropped. The full
per-row detail is still in `rejections.csv`. Nothing is silently trimmed.

Exit codes:

| Code | Meaning |
|---|---|
| `0` | ran, report stored, `failOn` policy not tripped |
| `1` | findings at or above the `failOn` threshold (the report is still stored) |
| `2` | input missing, invalid, oversized, or containing an unknown field |
| `3` | runtime or storage failure |

### Pricing

Pay per event, two events, charged **once per run**:

| Event | Price | Charged when |
|---|---|---|
| `feed-normalization` | **$0.10** | once, after a report **and** the normalized native rows have been written to both the dataset and `OUTPUT` |
| `feed-report` | **$0.05** | once, after a report alone has been written to both the dataset and `OUTPUT` |

Pricing is per **run**, not per row: a 200-row feed and a 200,000-row feed cost
the same. You can price a validation into your own pipeline without knowing the
catalogue size in advance.

Nothing else is billable. In particular you are **not** charged for:

- a rejected, invalid or oversized input (exit `2`),
- a runtime failure (exit `3`),
- a run whose `failOn` gate tripped (exit `1`) — findings are news you asked
  for, and that run is a failed run,
- a run whose result could not be stored,
- a start, a retry or a resurrect.

The charge is issued once per run, with an idempotency key, only after the
result is durably stored, and the run's exit code never depends on whether the
billing call succeeded. Your maximum cost per run is respected before any charge
is sent.

Those two prices are what this repository's code, tests and ledger agree on.
Whether they are what a published listing charges depends on the Apify Console
configuration, which is not part of this repository.

### Security

- **No network egress.** The run performs no outbound requests except to the
  Apify platform API that stores the result and issues the charge. There is no
  input field naming a URL, host or proxy, and unknown fields are rejected, so
  there is no user-influenced destination and no SSRF surface. `feedKey` is
  validated as a plain record name, so it cannot be pointed at a path.
- **No credentials.** No input field is a secret, because the Actor has nothing
  to authenticate to. `merchantName` is a public display name you choose to
  supply and is echoed back deliberately. The platform token the run receives is
  used only for storage and charging, and never appears in the dataset, the
  `OUTPUT` record, a CSV export or a log line.
- **Your feed is not retained.** It is read, validated and discarded with the
  run's own storage. The result carries a digest of the feed, never the feed.
- Row counts, feed size and result size are all capped, and the run executes as
  an unprivileged user in the container.

### Boundaries

- **Not an upload path.** It transmits nothing to OpenAI and holds no merchant
  account. Passing here is not submitting.
- **Not the ingest authority.** A clean report reduces rejection risk. It is not
  a guarantee of acceptance: OpenAI is the authority on its own ingest, and the
  specification can change ahead of this rule set.
- **Operator-side configuration cannot be checked.** Your registered merchant
  name, your configured market currencies and your Ads settings live in an
  account this tool cannot see. What it assumed is reported in every result as
  `assumptions` rather than presented as verified.
- **Links are checked for syntax only.** It does not fetch product or image
  URLs, so "must resolve with HTTP 200" is not something it can confirm.
- Not affiliated with or endorsed by OpenAI or Google.

### Licence

Apache-2.0.

# Actor input Schema

## `feedText` (type: `string`):

The feed itself, as CSV, TSV, JSON or NDJSON text. Use this for feeds small enough to paste; use `feedKey` for anything larger.

## `feedKey` (type: `string`):

Name of a record in this run's own default key-value store holding the feed. A plain record name, not a path and not an address; nothing outside this run's store can be read.

## `format` (type: `string`):

Force a parser instead of sniffing the content.

## `dialect` (type: `string`):

Which field-name set and rule set to apply.

## `merchantName` (type: `string`):

Your registered display name, used to check the `seller_name` rule. Supplied by you and taken on trust: AgentShelf has no merchant account and cannot verify it. Not a credential.

## `adsFeed` (type: `boolean`):

Apply the additional rules that only bind when the feed is used for Ads.

## `checkRecommended` (type: `boolean`):

Also report fields that are recommended rather than required. These are warnings and never block ingest on their own.

## `normalize` (type: `boolean`):

Map a Google-dialect feed into OpenAI native field names and include the mapped rows. Required for the accepted-rows CSV. This is the higher-priced pay-per-event outcome.

## `emitCsv` (type: `boolean`):

Write `rejections.csv`, and `accepted-rows.csv` when normalizing, as key-value store records. The CSV bodies are never inlined into the dataset item.

## `failOn` (type: `string`):

Which finding severity makes the run exit 1. `never` still fails a run that broke; it only suppresses findings.

## `maxRows` (type: `integer`):

Refuse a feed larger than this rather than validating a truncated prefix of it.

## `tenantId` (type: `string`):

Free-text label echoed into the result so you can attribute a run in your own records. Not used for anything else.

## Actor input object example

```json
{
  "format": "auto",
  "dialect": "auto",
  "adsFeed": false,
  "checkRecommended": true,
  "normalize": false,
  "emitCsv": true,
  "failOn": "error",
  "maxRows": 50000
}
```

# Actor output Schema

## `result` (type: `string`):

The single result as a dataset item: the input echo, the conformance summary, the rule tally, the per-row detail and references to the stored CSV records.

## `outputRecord` (type: `string`):

The identical result in the default key-value store under the OUTPUT key.

## `rejectionsCsv` (type: `string`):

One CSV row per finding: row index, source line, item id, severity, rule id, field, message and offending value. Written unless emitCsv was turned off, and absent on a failed run.

## `acceptedRowsCsv` (type: `string`):

The rows that would be accepted, projected into OpenAI native field names. Written only when normalize and emitCsv were both set.

# 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("savagnic/my-actor").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("savagnic/my-actor").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 savagnic/my-actor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,savagnic/my-actor"
        }
    }
}

```

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/7YKdMz9cn0ovIurZt/builds/MzO4cT8UlUcYAdeM7/openapi.json
