# Instagram Photo Post Image OCR (`automation-lab/instagram-photo-carousel-ocr`) Actor

Extract English image text from authorized public Instagram photo posts and the first five images of photo-only carousels. Export source-linked OCR text or no_text status, never substituted captions or video frames.

- **URL**: https://apify.com/automation-lab/instagram-photo-carousel-ocr.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (community)
- **Categories:** AI, Social media
- **Stats:** 2 total users, 1 monthly users, 66.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $12.06 / 1,000 photo post texts

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Instagram Photo Post Image OCR

Read the words displayed inside public Instagram photo posts. This focused **instagram posts scraper** accepts supplied photo URLs and returns image-derived text, per-image provenance, and an honest `no_text` status when no confident text is detected.

Use it for authorized campaign creative reviews, quote-card research, or comparing the wording on your own posts. It does not substitute captions or accessibility descriptions for OCR.

### What does it extract?

- English printed text from a single photo.
- Text from the first five images of a photo-only carousel, in source order.
- One row per completed unique post, including per-image text and status.
- Canonical post URL, shortcode, public author, image count and retrieval time.

Reels, videos, mixed photo/video carousels, private posts, Stories and profile discovery are excluded. Public access can change or require login; this Actor does not accept accounts, cookies or credentials.

### Who is it for?

Campaign analysts reviewing authorized creative, researchers comparing public quote cards, and content teams auditing their own image messaging can use these source-linked records.

### Why use this Actor?

Keep image text separate from post captions when researching creative messaging. Every image result retains its original position and temporary source image URL. Text-free images remain visible as `no_text` rather than becoming invented transcripts.

OCR runs locally within the Actor using Tesseract's English model. Images are auto-oriented and proportionally reduced to fit a 1280 × 1280 pixel OCR canvas, without upscaling; original source image links are retained. Images are not sent to a third-party AI inference service. OCR is imperfect: small or stylized lettering, low contrast and non-English content may be missed.

### Getting started

1. Choose public `/p/` post URLs you own or have permission to process.
2. Paste up to ten URLs into **Public photo post URLs**.
3. Optionally lower **Maximum unique posts** to process a prefix.
4. Run the Actor and inspect the default dataset.
5. Export JSON for nested per-image results, or CSV/Excel for post-level analysis.

Example input:

```json
{
  "startUrls": [{"url":"https://www.instagram.com/p/De0wagEDHtn/"}],
  "maxItems": 1
}
```

Public example posts may later disappear or change. Use your own authorized public photo URLs for repeatable production workflows.

### Input parameters

| Field | Meaning |
| --- | --- |
| `startUrls` | Required list of 1–10 HTTPS Instagram `/p/SHORTCODE/` URLs. String entries and URL objects are accepted. Tracking queries are removed. |
| `maxItems` | Integer 1–10, default 10. Maximum unique posts attempted globally, not images or successful rows. Zero/unlimited are unsupported. |

All supplied URLs are validated before selecting the unique prefix. Duplicate shortcodes are processed once. No hashtag, creator, keyword-search, proxy or language mode is exposed.

### Output fields

| Field | Meaning |
| --- | --- |
| `sourceUrl`, `canonicalUrl` | Normalized public post URL without tracking queries. |
| `shortcode`, `postId` | Instagram source identity. |
| `author` | Public username, or null if absent. |
| `contentType` | `photo` or `carousel`. |
| `totalImages`, `imagesRead` | Source image count and actual OCR count. |
| `truncated` | True when the carousel contains more than five photos. |
| `status` | `text` or `no_text`, based only on OCR output. |
| `text` | Accepted image text joined in order, with blank lines between nonempty images. |
| `images` | Image index, temporary CDN URL, OCR text and status for each processed image. |
| `ocrLanguage`, `retrievedAt` | `eng` and UTC completion time. |

Source image URLs can expire. Downloaded images are temporary working files and are deleted after processing; the Actor is not a media archive.

### Example output

A representative public single-photo test recovered the following wording from the image itself:

```json
{
  "canonicalUrl": "https://www.instagram.com/p/De0wagEDHtn/",
  "shortcode": "De0wagEDHtn",
  "contentType": "photo",
  "totalImages": 1,
  "imagesRead": 1,
  "truncated": false,
  "status": "text",
  "text": "Begin. Do it scared. Risk is\nalways better than regret.",
  "ocrLanguage": "eng"
}
```

This excerpt omits identity, timestamps and nested image provenance for brevity. Full dataset rows include the fields documented above.

### How does carousel OCR work?

The Actor first checks that every source child is a photo. A video anywhere in the carousel rejects the whole post, including a video after image five. For eligible carousels, it reads images one through five only, without reordering or replacing missing images.

If any selected image fails to download or OCR, that post is a failure rather than a falsely complete row. A carousel with eight photos reports `totalImages: 8`, `imagesRead: 5`, and `truncated: true`.

### What does no_text mean?

`no_text` means the installed English OCR engine produced no accepted text, not proof that an image contains no writing. Word confidence must be at least 85/100; isolated lines with fewer than three alphabetic characters are discarded as noise. Short codes, numbers-only labels, handwriting and decorative lettering may therefore be missed.

There is no caption or alt-text fallback. Failed retrievals never become `no_text`. Always review source images before using OCR for consequential decisions.

### How much does it cost to read Instagram photo text?

Billing uses a **$0.03 start charge per run** plus one **item** event for each post delivering readable image text. At the BRONZE tier, the item charge is **$0.0201 per text-bearing post**, including up to five images. Other plan tiers are shown in the Store pricing panel.

#### Worked examples — BRONZE totals including the start charge

- One text-bearing post: $0.0501.
- Five text-bearing posts in one run: $0.1305.
- Ten text-bearing posts in one run: $0.2310.

Posts without accepted text and failed posts incur only the run's start charge. Duplicate URLs do not create another item charge. These are Actor event prices, not a quote for unrelated platform services.

### Limits and failure behavior

- At most ten unique posts and five photos per post, processed sequentially.
- English OCR only; no speech transcription or video frame analysis.
- Two bounded anonymous browser sessions may attempt post retrieval.
- Maximum 15 MB and 40 million decoded pixels per downloaded image; a 1280-pixel maximum OCR dimension and a 60-second OCR timeout per image.
- Login walls, unavailable media, unknown media types and mixed carousels fail clearly.
- Completed earlier rows remain available as partial results if another post fails. The overall run is marked failed; inspect its logs and dataset.
- A platform charge limit can stop delivery before another text-bearing result is emitted.

No guarantees of complete or permanent Instagram access are implied. Repeatedly submitting a private or removed post will not make it accessible.

### Integrations

Export post-level text to Google Sheets for a creative inventory, route JSON to Make or Zapier for approved internal review, or load the dataset into a warehouse for comparing your campaigns. Preserve `canonicalUrl` as the source key and `imagesRead`/`truncated` when measuring coverage.

Scheduling repeats the supplied URLs only. It does not discover new posts or send change alerts. Implement comparison and notification logic in your own downstream workflow.

### API usage

Start a run with the Apify API, keeping your token secret:

```bash
curl -X POST 'https://api.apify.com/v2/acts/automation-lab~instagram-photo-carousel-ocr/runs' \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"startUrls":[{"url":"https://www.instagram.com/p/De0wagEDHtn/"}],"maxItems":1}'
```

JavaScript:

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/instagram-photo-carousel-ocr').call({
  startUrls: [{ url: 'https://www.instagram.com/p/De0wagEDHtn/' }], maxItems: 1,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems({ limit: 10 });
console.log(items);
```

Python:

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/instagram-photo-carousel-ocr').call(run_input={
    'startUrls': [{'url': 'https://www.instagram.com/p/De0wagEDHtn/'}], 'maxItems': 1,
})
print(client.dataset(run['defaultDatasetId']).list_items(limit=10).items)
```

An asynchronous run response contains metadata, not results. Wait for that same run's terminal status, then read its default dataset. Treat rows from a failed run as partial.

### Hosted MCP

Select a stable Actor-specific tool subset:

```bash
claude mcp add --transport http apify \
  'https://mcp.apify.com?tools=automation-lab/instagram-photo-carousel-ocr'
```

Claude Desktop, Cursor, and VS Code configuration (for clients supporting remote HTTP MCP):

```json
{"mcpServers":{"apify":{"url":"https://mcp.apify.com?tools=automation-lab/instagram-photo-carousel-ocr"}}}
```

Use your client's authorized Apify authentication. Discover actual tool names and argument schemas with `tools/list`; Actor selection also includes run/data helper tools, not exactly one tool.

Example prompt: “Read image text from these authorized public photo URLs, preserve per-image provenance, and distinguish no_text from failures.”

Start once and retain the run/storage IDs. A call may return before completion (`waitSecs` is at most 45 seconds). Check the same run with bounded backoff, for example 2, 4, 8 seconds capped at 10, and stop after a 120-second consumer deadline. On client timeout, recover the known run rather than starting it again. At deadline, report pending and its ID.

After success, read bounded dataset pages, for example 20 source rows per page and at most 100 rows or 64 KiB of serialized UTF-8 content admitted to model context, whichever comes first. Enforce byte limits host-side, disclose omitted content and continuation offsets, and keep full exports outside model context. If your client cannot intercept large results, do not claim a hard byte guarantee. These consumer context limits do not change collection limits. No universal token-savings claim is made.

### Legality

This Actor is independently developed by automation-lab and is not affiliated with or endorsed by Instagram or Meta.

### Responsible use and diagnostics

Process only public content you own or have permission to analyze. Public availability does not grant reuse rights. Respect applicable law, Instagram terms, copyright, privacy and downstream-use restrictions. This Actor does not bypass private-account access controls.

Public usernames, source image URLs and image text are stored in your run's default dataset under your Apify account's retention and access settings. Delete run storage in Console or through the API when no longer needed. Anonymous browser sessions and temporary images are removed at completion; logs contain post shortcodes and statuses, not OCR text or image bytes. Contact us through the Actor's Issues tab for support.

Failed operations send sanitized diagnostic input, exceptions and Actor/build/run IDs to our private GlitchTip service for repair. Secret fields and URL queries are removed; reports are retained for 30 days. No external AI service receives images for OCR.

### FAQ and troubleshooting

**Why is the caption missing?** Captions are not image text. This product intentionally exports OCR only.

**Why did a /p/ URL fail?** Instagram also uses /p/ URLs for videos and mixed carousels, or may hide public posts behind a login wall. Check source availability and media type.

**Why is readable text missing?** Try a clear, high-resolution English photo. Confidence filtering trades recall for reduced noise; OCR is not a verbatim guarantee.

**Can I pass a creator handle or a Reel?** No. Supply explicit public photo-post URLs. Use a dedicated video product for Reel OCR.

### Related Actors

- [Instagram Reel OCR & Hook Extractor](https://apify.com/automation-lab/instagram-reel-ocr-hooks) for video-frame text and hooks.
- [Instagram Post & Reel Details Scraper](https://apify.com/automation-lab/instagram-post-reel-details-scraper) for captions and media metadata rather than image OCR.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/instagram-photo-carousel-ocr/changelog.md

# Actor input Schema

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

Supply 1–10 HTTPS instagram.com/p/SHORTCODE/ URLs you are authorized to process. Duplicates are removed by shortcode. Videos and mixed carousels are rejected even when supplied as /p/ URLs. The entire supplied list is validated before maxItems selects its unique prefix. Only the url property is used; editor-provided method, payload, headers and userData are ignored. Requests always use anonymous GET.

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

Process this many unique supplied posts from the beginning of the list, globally per run (1–10, default 10). Counts posts attempted, not images or successful OCR rows. Zero and unlimited are unsupported. Each photo-only carousel reads at most five images in source order.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.instagram.com/p/De0wagEDHtn/"
    }
  ],
  "maxItems": 10
}
```

# Actor output Schema

## `dataset` (type: `string`):

One completed row per unique photo post.

# 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 = {
    "startUrls": [
        {
            "url": "https://www.instagram.com/p/De0wagEDHtn/"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/instagram-photo-carousel-ocr").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 = { "startUrls": [{ "url": "https://www.instagram.com/p/De0wagEDHtn/" }] }

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/instagram-photo-carousel-ocr").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 '{
  "startUrls": [
    {
      "url": "https://www.instagram.com/p/De0wagEDHtn/"
    }
  ]
}' |
apify call automation-lab/instagram-photo-carousel-ocr --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/instagram-photo-carousel-ocr"
        }
    }
}
```

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/fBQPTeqSadsBpTrFH/builds/gHmWVXSnQodpu2JNI/openapi.json
