# Receipt Scanner (`confidential_sand/receipt-scanner`)

Extract store name, date, total, items and more from receipt images or PDFs using AI-powered OCR. Ideal for expense tracking, finance automation, and data extraction workflows. Handles messy real-world formats with high accuracy.

- **URL**: https://beta-securitybyobscurity.apify.com/confidential\_sand/receipt-scanner.md
- **Developed by:** [Artur Malev](https://beta-securitybyobscurity.apify.com/confidential_sand) (community)
- **Categories:** AI, E-commerce, Automation
- **Stats:** 7 total users, 2 monthly users, 100.0% runs succeeded, NaN 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/platform/actors/running/actors-in-store#pay-per-usage

# README

## Receipt Scanner

**Receipt Scanner is a service for automatic extraction of structured data exclusively from receipts and invoices. Processing of other document types is not guaranteed. Upload is possible only via an image URL; direct file upload is not supported.**

Receipt Scanner is a universal tool for automatically extracting structured data from receipt images using modern AI providers (OpenAI and OpenRouter). The service is designed for quick integration into any business process where receipt, invoice, or document recognition and analysis are required.

### Purpose and Advantages

- **Automation of routine tasks**: Instantly extract information from receipts without manual input.
- **Flexible architecture**: Supports multiple AI providers and is easily extendable.
- **High accuracy**: Utilizes state-of-the-art models for text and document structure recognition.
- **Easy integration**: API and Python interface for rapid embedding.
- **Scalability**: Supports both single and batch processing with configurable concurrency.
- **Enhanced data extraction**: Extracts specialized receipt data including loyalty program information, return policies, warranty details, gift receipt indicators, and promotional codes.

### How It Works

1. The user provides a URL (or several URLs for batch processing) to an image of a receipt or invoice (only URL, direct file upload is not supported).
2. Receipt Scanner determines the provider (OpenAI or OpenRouter) and sends a request for analysis.
3. For batch processing, requests are executed in parallel with a configurable concurrency limit.
4. The AI model extracts key data: store, date, items, amounts, taxes, and currency.
5. Results are cached to speed up repeated processing of the same receipts.
6. The result is returned in a standardized JSON format.

### Usage Costs

The service is provided free of charge during the testing period.

In the future, the cost will be determined according to Apify's pricing. Current prices can be found on the [Apify pricing page](https://apify.com/pricing).

- **OpenAI**: Pricing according to [OpenAI official pricing](https://openai.com/pricing).
- **OpenRouter**: Alternative provider, see prices at [openrouter.ai/pricing](https://openrouter.ai/pricing).
- The final cost depends on the volume and complexity of images, selected model, and provider.

### Quick Start

#### 1. Install Dependencies

```bash
pip install -r requirements.txt
```

#### 2. Configure Environment Variables

```bash
export OPENAI_API_KEY=your_key
export OPENROUTER_API_KEY=your_alternative_key
## Optionally for caching
export REDIS_URL=redis://localhost:6379/0
export CACHE_TTL_SECONDS=86400
```

#### 3. Use in Python Code

##### Processing a single URL (backward compatibility)

```python
from src.utils import process_receipt_url
result = process_receipt_url("https://example.com/receipt.jpg")
print(result)
```

##### Batch processing of multiple URLs

```python
from src.utils import process_receipt_urls
urls = [
    "https://example.com/receipt1.jpg",
    "https://example.com/receipt2.jpg",
    "https://example.com/receipt3.jpg"
]
results = process_receipt_urls(urls, max_concurrent=5)
for result in results:
    print(f"URL: {result['url']}")
    print(f"Cached: {result.get('cached', False)}")
    if 'data' in result:
        data = result['data']
        print(f"Store: {data.get('store', {}).get('name')}")
        print(f"Total: {data.get('totals', {}).get('total')}")
        
        ## Access enhanced data fields
        if 'loyalty' in data:
            print(f"Loyalty Program: {data['loyalty'].get('program_name')}")
            print(f"Points Earned: {data['loyalty'].get('points_earned')}")
        
        if 'return_policy' in data:
            print(f"Return Period: {data['return_policy'].get('days_allowed')} days")
        
        if 'promotions' in data and 'promo_codes' in data['promotions']:
            print(f"Promo Codes: {', '.join(data['promotions']['promo_codes'])}")
        
        if 'gift_receipt' in data and data['gift_receipt'].get('is_gift_receipt'):
            print("This is a gift receipt")
    elif 'error' in result:
        print(f"Error: {result['error']}")
```

#### 4. API Usage

```bash
curl -X POST http://localhost:3000/api/v1/process \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/receipt.jpg"}'
```

##### Batch Processing of Multiple URLs

```bash
curl -X POST http://localhost:3000/api/v1/process-batch \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://example.com/receipt1.jpg", "https://example.com/receipt2.jpg"]}'
```

### API Usage

#### Input Schema

The Receipt Scanner expects a JSON object as input with the following fields:

- `receiptUrls` (array of strings, required): Array of receipt image URLs to be scanned. Example:

```json
{
  "receiptUrls": [
    "https://example.com/receipt1.jpg",
    "https://example.com/receipt2.jpg"
  ],
  "maxConcurrent": 5
}
```

- `maxConcurrent` (integer, optional): Maximum number of concurrent requests for batch processing. Default is 5. Minimum is 1, maximum is 20.

##### Example Input

```json
{
  "receiptUrls": [
    "https://example.com/receipt1.jpg",
    "https://example.com/receipt2.jpg"
  ],
  "maxConcurrent": 3
}
```

##### Example Output (Success)

```json
[
  {
    "url": "https://example.com/receipt1.jpg",
    "cached": false,
    "data": {
      "store": "Walmart",
      "date": "2024-06-01",
      "items": [
        {"name": "Milk", "price": 2.99, "qty": 1},
        {"name": "Bread", "price": 1.49, "qty": 2}
      ],
      "total": 5.97,
      "currency": "USD"
    }
  },
  {
    "url": "https://example.com/receipt2.jpg",
    "cached": true,
    "data": {
      "store": "Target",
      "date": "2024-06-02",
      "items": [
        {"name": "Eggs", "price": 3.49, "qty": 1}
      ],
      "total": 3.49,
      "currency": "USD"
    }
  }
]
```

##### Example Output (Error)

```json
[
  {
    "url": "https://example.com/invalid.jpg",
    "cached": false,
    "error": {
      "code": "INVALID_URL",
      "message": "Invalid or missing URL provided."
    }
  }
]
```

#### Example Response (Single Receipt)

```json
{
  "store": {
    "name": "Sample Store",
    "address": "Moscow, Example St., 1",
    "date": "2024-06-01",
    "time": "14:23",
    "currency": "RUB"
  },
  "items": [
    {
      "name": "Apple",
      "category": "food",
      "quantity": 3,
      "unit_price": 0.5,
      "total_price": 1.5
    }
  ],
  "totals": {
    "subtotal": 1.5,
    "tax": 0.15,
    "total": 1.65,
    "currency": "RUB"
  },
  "loyalty": {
    "program_name": "Store Rewards",
    "member_id": "12345678",
    "points_earned": 15,
    "points_balance": 230,
    "tier_status": "Gold",
    "expiration_date": "2025-12-31"
  },
  "return_policy": {
    "days_allowed": 30,
    "conditions": "Items must be in original packaging",
    "receipt_required": true,
    "restocking_fee": 0,
    "exceptions": "Sale items are final sale"
  },
  "warranty": {
    "duration": "1 year",
    "coverage": "Parts and labor",
    "contact_info": "support@example.com",
    "conditions": "Valid with receipt only",
    "product_registration": "Register at example.com/warranty"
  },
  "gift_receipt": {
    "is_gift_receipt": false
  },
  "promotions": {
    "promo_codes": ["SUMMER10", "NEXT5OFF"],
    "future_discounts": ["10% off next purchase"],
    "special_offers": ["Buy 2 Get 1 Free on all produce next week"],
    "expiration_dates": ["2024-07-15"],
    "conditions": "Cannot be combined with other offers"
  }
}
```

#### Example Response (Batch Processing)

```json
[
  {
    "url": "https://example.com/receipt1.jpg",
    "data": {
      "store": { "name": "Store 1", "date": "2024-06-01" },
      "items": [ { "name": "Item 1", "total_price": 10.5 } ],
      "totals": { "total": 10.5 },
      "loyalty": { 
        "program_name": "Store Rewards", 
        "points_earned": 10 
      },
      "return_policy": { 
        "days_allowed": 30 
      },
      "promotions": {
        "promo_codes": ["SUMMER10"]
      }
    },
    "cached": false
  },
  {
    "url": "https://example.com/receipt2.jpg",
    "data": {
      "store": { "name": "Store 2", "date": "2024-06-02" },
      "items": [ { "name": "Item 2", "total_price": 15.75 } ],
      "totals": { "total": 15.75 },
      "gift_receipt": {
        "is_gift_receipt": true,
        "gift_message": "Happy Birthday!"
      }
    },
    "cached": true
  },
  {
    "url": "https://example.com/invalid.jpg",
    "error": {
      "code": "PROCESSING_ERROR",
      "message": "Failed to process receipt image"
    }
  }
]
```

### Features

- Text recognition on receipt and invoice images (including handwritten and blurry ones).
- Item categorization.
- Support for various currencies and languages.
- Flexible error handling and data validation.
- Simple integration into third-party services via API.
- **Batch processing** — Ability to process multiple receipt URLs simultaneously.
- **Result caching** — Storing processed results by image hash to avoid redundant processing.
- **Parallel execution** — Configurable concurrency limits for optimal performance.
- **Enhanced data extraction** — Extracts specialized receipt data including:
  - **Loyalty program information** — Program name, member ID, points earned/balance, tier status
  - **Return policy details** — Return period, conditions, receipt requirements
  - **Warranty information** — Duration, coverage, contact information
  - **Gift receipt identification** — Detects if the receipt is a gift receipt
  - **Promotional codes** — Future discounts, special offers, expiration dates

### Limitations

- Recognition quality depends on the image quality and the selected model.
- Valid API keys are required for operation.
- Providers may impose rate and volume limits.
- Processing is possible only for receipts and invoices. Other document types are not supported or guaranteed.

### Testing

```bash
pytest tests/
```

### Cloudflare Worker

An example Worker implementation is available in the `cloudflare/` directory. It
handles the same JSON payloads as the Python service and can cache results in a
KV namespace. Deploy it with `wrangler`:

```bash
npm install -g wrangler
wrangler kv:namespace create receipt-scanner-cache
wrangler secret put OPENAI_API_KEY ## and other keys as needed
wrangler deploy
```

Set `RECEIPT_ANALYSIS_PROVIDER` and `CACHE_TTL_SECONDS` (or `CACHE_TTL`) to match the Python configuration. Other secrets like `OPENROUTER_API_KEY` and `OPENAI_ASSISTANT_ID` are optional depending on the provider.

If the KV binding is not configured, the worker still functions but without
caching.

### Useful Links

- [OpenAI Documentation](https://platform.openai.com/docs)
- [OpenRouter Documentation](https://openrouter.ai/docs)
- [Apify Web Scraping 101](https://docs.apify.com/web-scraping-101)
- [Crawlee — scraping library](https://crawlee.dev)

### License

MIT

# Actor input Schema

## `receiptUrls` (type: `array`):

Array of receipt image URLs to be scanned.

## `maxConcurrent` (type: `integer`):

Maximum number of concurrent requests for batch processing.

## Actor input object example

```json
{
  "receiptUrls": [
    "https://example.com/receipt.jpg"
  ],
  "maxConcurrent": 5
}
```

# 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 = {
    "receiptUrls": [
        "https://example.com/receipt.jpg"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("confidential_sand/receipt-scanner").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 = { "receiptUrls": ["https://example.com/receipt.jpg"] }

# Run the Actor and wait for it to finish
run = client.actor("confidential_sand/receipt-scanner").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "receiptUrls": [
    "https://example.com/receipt.jpg"
  ]
}' |
apify call confidential_sand/receipt-scanner --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=confidential_sand/receipt-scanner",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Receipt Scanner",
        "description": "Extract store name, date, total, items and more from receipt images or PDFs using AI-powered OCR. Ideal for expense tracking, finance automation, and data extraction workflows. Handles messy real-world formats with high accuracy.",
        "version": "0.0",
        "x-build-id": "u2yaC9fazuwXkH6JS"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/confidential_sand~receipt-scanner/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-confidential_sand-receipt-scanner",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/confidential_sand~receipt-scanner/runs": {
            "post": {
                "operationId": "runs-sync-confidential_sand-receipt-scanner",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/confidential_sand~receipt-scanner/run-sync": {
            "post": {
                "operationId": "run-sync-confidential_sand-receipt-scanner",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "receiptUrls"
                ],
                "properties": {
                    "receiptUrls": {
                        "title": "Receipt Image URLs",
                        "minItems": 1,
                        "type": "array",
                        "description": "Array of receipt image URLs to be scanned.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "maxConcurrent": {
                        "title": "Max Concurrent Requests",
                        "minimum": 1,
                        "maximum": 20,
                        "type": "integer",
                        "description": "Maximum number of concurrent requests for batch processing.",
                        "default": 5
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
