# AI Document & Web Analyzer — powered by OpenAI GPT (`badr_xba/ai-document-web-analyzer-openai`) Actor

Summarize, classify or extract JSON from web pages, PDFs, Word files, images, texts and any Apify dataset with OpenAI GPT models. Bring your own OpenAI API key. Token caps, an estimate-only mode and a visual report. Not affiliated with OpenAI.

- **URL**: https://apify.com/badr_xba/ai-document-web-analyzer-openai.md
- **Developed by:** [Badr Bouargan](https://apify.com/badr_xba) (community)
- **Categories:** AI, Developer tools, Automation
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 item analyzed (own key)s

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

## AI Document & Web Analyzer — powered by OpenAI GPT

Run OpenAI GPT models on **web pages, PDFs, Word / PowerPoint / Excel files, images, raw texts and the datasets of any other Apify Actor**. Every item becomes one row: summary, key points, your extracted JSON fields, labels, key facts, a report section or a comparison. You also get token counts, the estimated OpenAI cost and a visual `REPORT` page.

> Independent product, not affiliated with or endorsed by OpenAI. "OpenAI" and "GPT" are named only to say which models the Actor uses.

![AI Document & Web Analyzer powered by OpenAI GPT: features overview](https://api.apify.com/v2/key-value-stores/R1P4OnWyoV0LnGBin/records/ai-openai-hero.png)

### Why use it

- **Strict JSON you can load straight into a database.** Every answer uses OpenAI **Structured Outputs in strict mode**; `extract` follows **your JSON Schema**, checked against the strict-mode rules *before* the run starts.
- **Office files, not only PDFs.** PDF, DOCX, PPTX, XLSX, TXT, MD, CSV and JSON documents up to 50 MB.
- **One step after any scraper.** Pass the dataset of Website Content Crawler, Amazon, Instagram, Facebook Ads or LinkedIn Jobs scrapers and add an AI column to every row.
- **Cost under control.** Estimate-only runs, a per-run cost cap (`maxCostUsd`, default $2), token caps and a per-item part limit.
- **Honest output.** Long content is never cut silently, images are the source's own (up to 3), and a confidence value is shown only when the model gave one *with* a basis, labelled "model-reported". The Actor computes no scores.

### What OpenAI brings to this Actor

These are the OpenAI API features the code actually uses (see `src/provider.py`, `src/analyzer.py`, `src/prompts.py`):

| OpenAI feature | What it means for you |
|---|---|
| **Responses API with Structured Outputs** (`json_schema`, `strict: true`) | The answer must match the schema. Your `outputSchema` is checked first: root must be an object, every object gets `additionalProperties: false`, fields not listed as required become nullable; `allOf`, `not`, `if/then/else`, `patternProperties`… are rejected with a clear message. |
| **Vision** (`input_image`) | `imageUrls`, and optionally the images of web pages and dataset items (`analyzePageImages`). |
| **Native file input** (`input_file`) | PDFs are sent with their text **and page images**; Office files are sent as text. |
| **Reasoning effort** per level (`reasoning.effort`) | low / medium / high by level, or force it with `reasoningEffort`. Reasoning tokens are billed as output. |
| **Image detail** (`imageDetail`) | `low` = at most ~308 tokens per image or PDF page; `high` = at most ~3,000. |
| **Explicit prompt caching** | The shared instructions are cached only when that saves money. |
| **Token counting** (`responses.input_tokens.count`) | Optional exact input counts in estimate-only runs (`exactTokenCount`). |
| **Streaming**, `store: false` | Answers are streamed; requests ask OpenAI not to store the response. |

### Accepted sources

| Source | Input field | Details |
|---|---|---|
| Web pages | `startUrls` | One plain HTTP GET per page with an honest User-Agent; no browser, no JavaScript, no anti-bot bypass; robots.txt Disallow honoured (skipped pages go to `FAILED_INPUTS`). A URL that serves a PDF, Word file or image is analysed as a document or image. |
| Raw texts | `texts` | Emails, reviews, transcripts. Through the API each entry can be a long string with line breaks. |
| Documents | `documentUrls` | PDF, DOCX, PPTX, XLSX, TXT, MD, CSV, JSON, up to 50 MB each. Old `.doc` files are not supported. |
| Images | `imageUrls` | PNG, JPEG, WEBP, GIF, analysed with GPT vision. |
| Apify datasets | `datasetIds` + `datasetFields` | Each item is analysed. Dot paths such as `metadata.description`. Empty `datasetFields` = the usual text fields (title, text, markdown, description, review…). Your account must be able to read the dataset. |

### Use cases

- **Product data teams:** turn Amazon titles, feature bullets and descriptions into a strict specs schema (brand, capacity, material, warranty…).
- **Back office:** extract invoice numbers, totals and currencies from PDFs, spreadsheets (XLSX) or slide decks (PPTX).
- **Social listening:** classify Instagram captions or Facebook ads by topic, tone or offer type.
- **Content audits:** summarize every page of a Website Content Crawler run with key points and entities.
- **Visual checks:** read receipts, screenshots and product photos with GPT vision.
- **Vendor comparison:** `compare` adds one comparison row over all analysed items.

### How it works

![How it works: scraper datasets, URLs, documents and texts go through the Actor and come out as JSON rows and a report](https://api.apify.com/v2/key-value-stores/R1P4OnWyoV0LnGBin/records/ai-openai-how-it-works.png)

1. **Fetch and check** every source (public addresses only, at most 5 redirects, size caps, robots.txt).
2. **Estimate and guard**: input tokens are estimated and checked against `maxCostUsd` / `maxTotalTokens` *before* each call.
3. **Analyse** with the GPT model of your level at its reasoning effort; long content is split into parts, condensed, then analysed.
4. **Validate** the strict JSON answer and write one row per item, then `REPORT`, `RUN_SUMMARY` and `FAILED_INPUTS`.

### Tasks

| `task` | What you get |
|---|---|
| `summarize` (default) | title, summary, key points, entities, important info |
| `extract` | the fields of **your JSON Schema** (`outputSchema`) in `result` |
| `classify` | your `labels` (single, or several with `multiLabel`), each with a stated basis |
| `analyze` | topics, insights, tone |
| `detect_key_info` | dates, amounts, contacts, deadlines, obligations |
| `compare` | every item, plus **one comparison row** over all analysed items (needs at least 2) |
| `report` | report sections |
| `custom` | the answer to your `instructions` |

### Analysis levels and models

From `src/models.json` (OpenAI list prices read 2026-11-09):

| `analysisLevel` | Model | Reasoning | Max output tokens | Image detail | Typical web item\* |
|---|---|---|---|---|---|
| `quick` | `gpt-6-luna` | low | 6,000 | low | ≈ $0.0006 |
| `standard` (default) | `gpt-6.1-sol` | medium | 16,000 | high | ≈ $0.019 |
| `deep` | `gpt-6-astra` | high | 32,000 | high | ≈ $0.19 |

\*OpenAI list price for 1,500 tokens of text in and about 500 visible tokens out; an estimate, not a measured run. Reasoning tokens are billed as output.

Force a model with `model`:

| `model` | Input / cached input / output per 1M tokens | Above 272K input tokens |
|---|---|---|
| `gpt-6-luna` | $0.10 / $0.01 / $0.50 | $0.20 / $0.02 / $0.75 |
| `gpt-6.1-terra` | $1.00 / $0.05 / $5.00 | $2.00 / $0.10 / $7.50 |
| `gpt-6.1-sol` | $2.00 / $0.10 / $10.00 | $4.00 / $0.20 / $15.00 |
| `gpt-6-astra` | $10.00 / $1.00 / $50.00 | $20.00 / $2.00 / $75.00 |

All four have a 1,050,000-token context window and up to 128,000 output tokens. Above 272K input tokens the whole request is priced at the long-context rate (the conservative reading) and the row says `longContext: true`.

### Pricing

- **Your own OpenAI API key (available now): $0.0015 / $0.003 / $0.006 per analysed item** for quick / standard / deep (one `item-analyzed` event × 1, 2 or 4), plus Apify's start event of $0.00005 per run. OpenAI bills you directly for the AI tokens.
- Failed items, refusals and estimates are **never charged**.
- **Managed key (no API key needed): coming soon.** It is not open yet: a run with `apiKeyMode: "managed"` stops before any AI call with a clear message, and nothing is charged.

### API key setup

1. Create a key at [platform.openai.com/api-keys](https://platform.openai.com/api-keys).
2. Paste it into the **OpenAI API key** field (`apiKey`). It is an Apify **secret input**: stored encrypted, never logged, never stored in the output.
3. Leave **API key mode** (`apiKeyMode`) on `own_key`.

Without a key the run only estimates tokens and costs. Only the `apiKey` field is encrypted: if you send a key under another field name (for example `openaiApiKey`), the Actor ignores it and warns you without printing it. That value still sits in plain text in the run's input, so remove it and rotate the key.

### Input

Every field of the input schema (`.actor/input_schema.json`). No field is required, but a run needs at least one source.

| Field | Type | Default | Description |
|---|---|---|---|
| `startUrls` | array | — | Web pages to analyse (plain HTTP, robots.txt honoured). |
| `texts` | array | — | Raw texts, one item per entry. |
| `documentUrls` | array | — | PDF, DOCX, PPTX, XLSX, TXT, MD, CSV or JSON links (50 MB max each). |
| `imageUrls` | array | — | PNG, JPEG, WEBP or GIF images (GPT vision). |
| `datasetIds` | array | — | IDs of datasets produced by other Actors. |
| `datasetFields` | array | — | Fields of each dataset item to send (dot paths). Empty = the usual text fields. |
| `task` | string | `"summarize"` | `summarize`, `extract`, `classify`, `analyze`, `detect_key_info`, `compare`, `report`, `custom`. |
| `instructions` | string | — | Extra instructions in plain words; needed in practice for `custom`. |
| `outputSchema` | object | — | JSON Schema for `extract`, checked against the Structured Outputs rules. |
| `labels` | array | — | Allowed labels for `classify`. |
| `multiLabel` | boolean | `false` | `classify`: allow more than one label per item. |
| `language` | string | — | Language of the answers. Empty = the language of each source. |
| `analysisLevel` | string | `"standard"` | `quick` (GPT-6 Luna), `standard` (GPT-6.1 Sol), `deep` (GPT-6 Astra). |
| `model` | string | — | Optional override: `gpt-6-luna`, `gpt-6.1-terra`, `gpt-6.1-sol`, `gpt-6-astra`. |
| `reasoningEffort` | string | — | Optional override: `none`, `low`, `medium`, `high`, `xhigh`, `max` (unsupported values fall back to the level default). |
| `imageDetail` | string | — | `low` or `high`. Empty = level default (quick: low, others: high). |
| `analyzePageImages` | boolean | `false` | Also send the (up to 3) images of web pages and dataset items to GPT vision. |
| `maxItems` | integer | `50` | Maximum number of items analysed in this run (1 to 10,000). |
| `maxInputTokensPerItem` | integer | `100000` | Longer content is split into parts (2,000 to 922,000). Above 272,000 OpenAI charges long-context prices. |
| `maxOutputTokensPerItem` | integer | `0` | Includes reasoning tokens. 0 = level default (6,000 / 16,000 / 32,000). |
| `maxChunksPerItem` | integer | `10` | Parts beyond this number are not analysed and the row says `truncated: true` (1 to 100). |
| `maxTotalTokens` | integer | `0` | The run stops before a call that could exceed this total. 0 = no limit. |
| `maxCostUsd` | number | `2` | The run stops before a call whose worst case could push the estimated OpenAI cost above this (USD). 0 = no limit. |
| `estimateOnly` | boolean | `false` | Download the sources and estimate input tokens and the maximum cost per item, without calling the model. |
| `exactTokenCount` | boolean | `false` | With `estimateOnly` and your own key: ask OpenAI's token-counting endpoint for exact input tokens. |
| `concurrency` | integer | `3` | Items analysed at the same time (1 to 10). |
| `itemTimeoutSecs` | integer | `0` | 0 = automatic: one answer of the full output budget at 30 tokens/s + 60 s (at least 300 s). |
| `apiKeyMode` | string | `"own_key"` | `own_key` (available) or `managed` (coming soon, not open yet). |
| `apiKey` | string (secret) | — | Your OpenAI API key. |

### Output

One dataset row per item (views: **Overview**, **Structured results**, **Tokens and cost**). Fields from `.actor/dataset_schema.json`:

| Field | Type | Description |
|---|---|---|
| `source` | string | URL, `text #n`, `dataset:<id>#<index>` or the compared sources. |
| `sourceType` | string | `web`, `text`, `document`, `image`, `dataset` or `comparison`. |
| `task` | string | Task run on the item. |
| `model` | string | OpenAI model that answered. |
| `analysisLevel` | string | `quick`, `standard` or `deep`. |
| `reasoningEffort` | string | Reasoning effort sent to the model (null = model default). |
| `title` | string | Title of the source or the one the model found. |
| `summary` | string | Summary written by the model. |
| `keyPoints` | array | Key points. |
| `result` | object | Task result: your extracted fields, labels, topics, key facts, report sections, comparison table… |
| `labels` | array | `classify` only: labels given WITH a stated basis; confidence is model-reported (null when the model gave none). |
| `entities` | array | People, organisations, places, products named in the source. |
| `importantInfo` | array | Risks, deadlines, amounts, obligations or warnings. |
| `language` | string | Language of the answer. |
| `images` | array | Up to 3 real image URLs from the source (never generated). |
| `tokens` | object | `{input, cachedInput, cacheWrite, output, reasoning}` as reported by OpenAI. |
| `costUsd` | number | Estimated OpenAI cost at list prices; 0 for estimate-only rows. |
| `billedEvents` | object | Apify pay-per-event events charged for this row. |
| `durationMs` | integer | Time spent on the item. |
| `truncated` | boolean | True when part of a very long source was not analysed. |
| `truncationReason` | string | Why and how much was left out. |
| `chunks` | integer | Parts the source was split into (1 = sent whole). |
| `longContext` | boolean | More than 272K input tokens: long-context prices applied. |
| `estimateOnly` | boolean | Row from an estimate-only run (no AI call). |
| `estimatedInputTokens` | integer | Local estimate of the input tokens of the direct request. |
| `estimatedMaxCostUsd` | number | Estimate-only: worst-case OpenAI cost of the item. |
| `warnings` | array | Notes about this item (`{code, message}`). |
| `error` | object | `{code, message}` when the item gave no analysis (also listed in `FAILED_INPUTS`). |
| `datasetId` | string | Dataset the item came from. |
| `datasetIndex` | integer | Position of the item in that dataset. |

Key-value store: `REPORT` (visual page), `RUN_SUMMARY`, `FAILED_INPUTS` (every input that gave no analysis, with a code).

![REPORT page cards, rendered from a local demo run with canned answers](https://api.apify.com/v2/key-value-stores/R1P4OnWyoV0LnGBin/records/ai-openai-report-demo.png)

*Illustration — demo data, not a real AI answer. Screenshot of the real `REPORT` page layout, rendered from a local mock run (`MOCK_PROVIDER=1`): every text marked `[MOCK]` is canned, no model was called.*

![REPORT page statistics, rendered from a local demo run](https://api.apify.com/v2/key-value-stores/R1P4OnWyoV0LnGBin/records/ai-openai-report-overview-demo.png)

*Illustration — demo data, not a real AI answer. Run statistics of the same local mock run.*

### Examples

Summarize a page, cheapest level:

```json
{
  "startUrls": [{ "url": "https://docs.apify.com/platform/actors" }],
  "task": "summarize",
  "analysisLevel": "quick",
  "apiKey": "sk-..."
}
```

Extract invoice fields from PDFs, spreadsheets or slides (strict schema):

```json
{
  "documentUrls": ["https://example.com/invoice.pdf", "https://example.com/invoices-q3.xlsx"],
  "task": "extract",
  "outputSchema": {
    "type": "object",
    "properties": {
      "invoiceNumber": { "type": "string" },
      "total": { "type": "number" },
      "currency": { "type": "string" }
    },
    "required": ["invoiceNumber"]
  },
  "apiKey": "sk-..."
}
```

Read receipts with vision, low detail to save tokens:

```json
{
  "imageUrls": ["https://example.com/receipt-0912.jpg"],
  "task": "detect_key_info",
  "imageDetail": "low",
  "apiKey": "sk-..."
}
```

Estimate a big job with OpenAI's exact token count:

```json
{
  "documentUrls": ["https://example.com/annual-report.pdf"],
  "task": "report",
  "analysisLevel": "deep",
  "estimateOnly": true,
  "exactTokenCount": true,
  "apiKey": "sk-..."
}
```

### Integrations: chain it after a scraper

Every Apify Actor writes its results to a dataset. Pass that dataset's ID in `datasetIds` and choose the fields with `datasetFields`. The field names below are the real output fields of each scraper.

**In the Apify Console (no code):** open the scraper (or its task) → **Integrations** → **Connect Actor or task** → choose this Actor and use an input such as `{"datasetIds": ["{{resource.defaultDatasetId}}"], ...}`. Each finished scraper run then starts an analysis of its own results.

**Website Content Crawler** ([apify/website-content-crawler](https://apify.com/apify/website-content-crawler)) — summarize a crawl:

```json
{
  "datasetIds": ["<datasetId of your crawler run>"],
  "datasetFields": ["url", "metadata.title", "text"],
  "task": "summarize",
  "analysisLevel": "quick",
  "maxItems": 500,
  "apiKey": "sk-..."
}
```

**Amazon Product Scraper** ([badr_xba/amazon-product-scraper](https://apify.com/badr_xba/amazon-product-scraper)) — strict specs table:

```json
{
  "datasetIds": ["<datasetId of your amazon-product-scraper run>"],
  "datasetFields": ["title", "brand", "features", "description", "attributes"],
  "task": "extract",
  "outputSchema": {
    "type": "object",
    "properties": {
      "productType": { "type": "string" },
      "material": { "type": ["string", "null"] },
      "capacityOrSize": { "type": ["string", "null"] },
      "warrantyMonths": { "type": ["integer", "null"] }
    },
    "required": ["productType", "material", "capacityOrSize", "warrantyMonths"]
  },
  "analysisLevel": "quick",
  "apiKey": "sk-..."
}
```

**Instagram Scraper** ([badr_xba/instagram-scraper](https://apify.com/badr_xba/instagram-scraper)) — classify post captions, with the post image:

```json
{
  "datasetIds": ["<datasetId of your instagram-scraper run>"],
  "datasetFields": ["ownerUsername", "caption", "hashtags", "likesCount"],
  "task": "classify",
  "labels": ["product launch", "promotion", "behind the scenes", "user content", "other"],
  "analyzePageImages": true,
  "imageDetail": "low",
  "analysisLevel": "quick",
  "apiKey": "sk-..."
}
```

**Facebook Ads Library Scraper** ([badr_xba/facebook-ads-library-scraper](https://apify.com/badr_xba/facebook-ads-library-scraper)) — analyse competitors' ad messaging:

```json
{
  "datasetIds": ["<datasetId of your facebook-ads-library-scraper run>"],
  "datasetFields": ["pageName", "snapshot.title", "snapshot.body.text", "snapshot.ctaText", "daysRunning"],
  "task": "analyze",
  "instructions": "Name the offer, the promise and the target audience of each ad.",
  "apiKey": "sk-..."
}
```

**LinkedIn Jobs Scraper** ([badr_xba/linkedin-jobs-scraper](https://apify.com/badr_xba/linkedin-jobs-scraper)) — key facts from job posts:

```json
{
  "datasetIds": ["<datasetId of your linkedin-jobs-scraper run>"],
  "datasetFields": ["title", "companyName", "location", "employmentType", "descriptionText"],
  "task": "detect_key_info",
  "analysisLevel": "quick",
  "apiKey": "sk-..."
}
```

**From code (apify-client for JavaScript):** run a scraper, then analyse its dataset.

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const scrape = await client.actor('badr_xba/amazon-product-scraper').call({
    categoryOrProductUrls: [{ url: 'https://www.amazon.com/s?k=keyboard' }], maxItemsPerStartUrl: 10,
});
const analysis = await client.actor('badr_xba/ai-document-web-analyzer-openai').call({
    datasetIds: [scrape.defaultDatasetId],
    datasetFields: ['title', 'brand', 'features', 'description'],
    task: 'summarize', analysisLevel: 'quick', apiKey: process.env.OPENAI_API_KEY,
});
const { items } = await client.dataset(analysis.defaultDatasetId).listItems();
```

### Cost control

- **`estimateOnly`**: downloads the sources and estimates input tokens and the maximum cost per item without calling the model. Add `exactTokenCount` for OpenAI's own count.
- **`maxCostUsd`** (default $2) and **`maxTotalTokens`**: the run stops before a call whose worst case could go over.
- **`maxInputTokensPerItem`** + **`maxChunksPerItem`**: above the limit the text is split into parts, each part is condensed, then the task runs on the notes. If parts still have to be left out, the row says `truncated: true` and gives the reason.
- **`imageDetail: "low"`**: at most ~308 tokens per image or PDF page instead of ~3,000.
- **`quick` level**: GPT-6 Luna is about 1/20 of GPT-6.1 Sol's token price.
- **`itemTimeoutSecs`**: 0 = automatic, long enough for the full output budget (deep level ≈ 19 min), because OpenAI still bills an answer cut by a timeout.

### Limits and errors

- No JavaScript rendering: pages that need a browser come back as "no readable text". Crawl them first with Website Content Crawler, then pass its dataset.
- Old `.doc` files are not supported. Scanned PDFs are read from their page images only.
- With your own key, token estimates before the call are local approximations (about 4 characters per token). Costs in the rows are estimates at list prices; your OpenAI invoice is the reference.
- Failed inputs are listed in `FAILED_INPUTS` with a code; the row's `error` is `{code, message}`. Warnings use codes such as `truncated`, `schemaMismatch`, `longContext`, `noTextLayer`, `compareSkipped` (fewer than two items analysed) and `keyIgnored`.
- Prices and model capabilities were taken from OpenAI's official pages on 2026-11-09 (`src/models.json`).

#### Troubleshooting

| Status message / FAILED_INPUTS code | Cause | Fix |
|---|---|---|
| "Stopped: OpenAI rejected the API key — nothing was analysed or billed." / `invalidApiKey` | The key in "OpenAI API key" is wrong, revoked, expired or mistyped. The run stops after the first refused request (no other item is sent), lists every item in `FAILED_INPUTS`, sets `stoppedReason` in `RUN_SUMMARY` and ends **FAILED** so that schedules and integrations notice it. Only Apify's run start event is charged. | Create or copy a key at https://platform.openai.com/api-keys and paste it, without spaces or quotes, in the "OpenAI API key" field (`apiKey`). |
| Same message / `apiKeyNoAccess` | The key is known but has no access (HTTP 403 / permission denied). | Check at https://platform.openai.com/api-keys that the key's project may use the chosen model and your region is supported. |
| `rateLimited`, `serverError` (or similar) | Rate limit (429) or provider outage (5xx): retried with back-off, never treated as a key error; the other items continue. | Run again later or lower `concurrency`. |
| No key at all | Not an error: the run is an estimate-only demo (SUCCEEDED, nothing billed). | Add your key to analyse. |

### Privacy

Your key is a secret input (encrypted by Apify). It is never logged or stored. Anything that looks like a key is removed from error messages and from crash tracebacks. Logs contain ids and lengths only, never your content. Requests are sent with `store: false`. Content goes to OpenAI under your API account terms. Every fetched host is resolved once and the connection goes to that exact validated IP (private, loopback, link-local and cloud-metadata addresses are refused, also after redirects), with the real host name kept for the `Host` header and TLS certificate checks, so DNS rebinding cannot reach an internal address.

### FAQ

**Do I need an OpenAI account?** Yes, for now: own-key mode is the only mode available. The managed key is coming soon.

**Are the answers in the screenshots real?** No. The report screenshots come from a local demo run with canned answers, to show the page layout.

**Why did my schema get rejected?** Strict Structured Outputs do not accept some JSON Schema keywords (`allOf`, `not`, `if/then/else`, `patternProperties`…). The run stops before any AI call and says which keyword to remove.

### Feedback and reviews

Something missing or wrong? Please open an issue on the Actor's Issues tab. We read them all. If the Actor saved you time, a short review on the Store helps other users find it.

# Changelog

This Actor's version history is a separate document: https://apify.com/badr_xba/ai-document-web-analyzer-openai/changelog.md

# Actor input Schema

## `startUrls` (type: `array`):

Pages to analyse. Each page is downloaded once with a plain HTTP request (no browser, no JavaScript, no anti-bot bypass); pages that robots.txt disallows are skipped and listed in FAILED_INPUTS. A URL that serves a PDF, Word file or image is analysed as a document or image.

## `texts` (type: `array`):

Raw texts, one per entry (emails, reviews, transcripts…). Through the API each entry can be a long string with line breaks.

## `documentUrls` (type: `array`):

Public links to PDF, DOCX, PPTX, XLSX, TXT, MD, CSV or JSON files (50 MB max each). PDFs are sent to OpenAI with their text and page images; Office files as text.

## `imageUrls` (type: `array`):

Public PNG, JPEG, WEBP or GIF images analysed with GPT vision (screenshots, receipts, product photos…).

## `datasetIds` (type: `array`):

IDs of datasets produced by other Actors (Website Content Crawler, Google Maps reviews, Amazon, Instagram…): each item is analysed. Your account must be able to read them.

## `datasetFields` (type: `array`):

Fields of each dataset item to send (dot paths such as "text" or "metadata.description"). Empty = the usual text fields (title, text, markdown, description, review…).

## `task` (type: `string`):

What the model does with each item. "Compare" also adds one comparison row over all items.

## `instructions` (type: `string`):

Optional extra instructions in plain words ("focus on pricing", "answer for a lawyer"). Required in practice for the Custom task.

## `outputSchema` (type: `object`):

For the Extract task: the JSON Schema of the fields you want, e.g. {"type":"object","properties":{"price":{"type":"number"},"currency":{"type":"string"}}}. It is checked before the run against OpenAI Structured Outputs rules; fields not listed as required become nullable.

## `labels` (type: `array`):

Allowed labels for the Classify task, e.g. positive, negative, neutral.

## `multiLabel` (type: `boolean`):

Classify: allow more than one label per item.

## `language` (type: `string`):

Language of the answers (e.g. English, French). Empty = the language of each source.

## `analysisLevel` (type: `string`):

Quick = GPT-6 Luna, low reasoning (cheapest). Standard = GPT-6.1 Sol, medium reasoning. Deep = GPT-6 Astra, high reasoning (most capable, ~5x Standard). See PRICING in the README.

## `model` (type: `string`):

Optional: force a model instead of the level default.

## `reasoningEffort` (type: `string`):

Optional. Higher effort = more (billed) reasoning tokens. Unsupported values for the chosen model fall back to the level default.

## `imageDetail` (type: `string`):

low = at most ~308 tokens per image or PDF page; high = at most ~3,000. Empty = level default (Quick: low, others: high).

## `analyzePageImages` (type: `boolean`):

Web pages and dataset items: send their (up to 3) images to GPT vision as well. Costs more tokens.

## `maxItems` (type: `integer`):

Maximum number of items analysed in this run.

## `maxInputTokensPerItem` (type: `integer`):

Longer content is split into parts and summarised in steps (never cut silently). Above 272,000 OpenAI charges long-context prices.

## `maxOutputTokensPerItem` (type: `integer`):

Includes reasoning tokens. 0 = level default (6,000 / 16,000 / 32,000).

## `maxChunksPerItem` (type: `integer`):

Cost guard for very long documents: parts beyond this number are not analysed and the row says truncated = true.

## `maxTotalTokens` (type: `integer`):

The run stops before a call that could exceed this total (input + output). 0 = no limit.

## `maxCostUsd` (type: `number`):

The run stops before a call whose worst case could push the estimated OpenAI cost (list prices) above this amount. 0 = no limit.

## `estimateOnly` (type: `boolean`):

Downloads the sources and estimates input tokens and the maximum cost per item, without calling the model.

## `exactTokenCount` (type: `boolean`):

With Estimate only and your own key: also ask OpenAI's token-counting endpoint for the exact input tokens of each item.

## `concurrency` (type: `integer`):

Items analysed at the same time. Lower it if your OpenAI rate limits are small.

## `itemTimeoutSecs` (type: `integer`):

An item that takes longer is recorded as failed; the run goes on. 0 = automatic: long enough for one answer of the full output budget at a slow 30 tokens/s + 60 s (at least 300 s; deep level ≈ 19 min). Managed key: a shorter value is raised to that minimum, because OpenAI still bills an answer cut by a timeout.

## `apiKeyMode` (type: `string`):

Own key: your OpenAI account pays the tokens; this Actor charges a small fee per analysed item. Managed: no OpenAI account needed, tokens are billed per 1,000 through Apify (paid Apify plans only).

## `apiKey` (type: `string`):

Your key from platform.openai.com (stored encrypted by Apify, never logged). Without a key the run only estimates tokens and costs.

## Actor input object example

```json
{
  "texts": [
    "Apify is a platform where developers build, deploy and publish web scrapers and automation tools called Actors. Actors store their results in datasets that other Actors can read, so a scraper and an AI step can be chained."
  ],
  "task": "summarize",
  "multiLabel": false,
  "analysisLevel": "quick",
  "analyzePageImages": false,
  "maxItems": 3,
  "maxInputTokensPerItem": 100000,
  "maxOutputTokensPerItem": 0,
  "maxChunksPerItem": 10,
  "maxTotalTokens": 0,
  "maxCostUsd": 0.5,
  "estimateOnly": false,
  "exactTokenCount": false,
  "concurrency": 3,
  "itemTimeoutSecs": 0,
  "apiKeyMode": "own_key"
}
```

# Actor output Schema

## `visualReport` (type: `string`):

Interactive page: one card per item (summary, key points, labels, extracted fields, the source's own images), run totals, comparison, errors panel.

## `overview` (type: `string`):

No description

## `results` (type: `string`):

No description

## `allFields` (type: `string`):

No description

## `runSummary` (type: `string`):

Items, tokens, estimated OpenAI cost, billed events, stop reason.

## `failedInputs` (type: `string`):

Inputs that gave no analysis, with the reason and code.

# 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 = {
    "texts": [
        "Apify is a platform where developers build, deploy and publish web scrapers and automation tools called Actors. Actors store their results in datasets that other Actors can read, so a scraper and an AI step can be chained."
    ],
    "task": "summarize",
    "analysisLevel": "quick",
    "maxItems": 3,
    "maxCostUsd": 0.5
};

// Run the Actor and wait for it to finish
const run = await client.actor("badr_xba/ai-document-web-analyzer-openai").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 = {
    "texts": ["Apify is a platform where developers build, deploy and publish web scrapers and automation tools called Actors. Actors store their results in datasets that other Actors can read, so a scraper and an AI step can be chained."],
    "task": "summarize",
    "analysisLevel": "quick",
    "maxItems": 3,
    "maxCostUsd": 0.5,
}

# Run the Actor and wait for it to finish
run = client.actor("badr_xba/ai-document-web-analyzer-openai").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 '{
  "texts": [
    "Apify is a platform where developers build, deploy and publish web scrapers and automation tools called Actors. Actors store their results in datasets that other Actors can read, so a scraper and an AI step can be chained."
  ],
  "task": "summarize",
  "analysisLevel": "quick",
  "maxItems": 3,
  "maxCostUsd": 0.5
}' |
apify call badr_xba/ai-document-web-analyzer-openai --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,badr_xba/ai-document-web-analyzer-openai"
        }
    }
}
```

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/zZGYEtZmW9CynsMHr/builds/g27PlOWu67uoTE6cj/openapi.json
