# Image Metadata & EXIF Extractor – GPS, IPTC, AI Check, Hash (`gazidev/image-metadata-extractor`) Actor

Bulk-read image metadata from URLs: EXIF camera data, GPS with nearest city, IPTC/XMP credits and copyright, AI-generation signals (C2PA, IPTC, SD prompts), dimensions, perceptual hashes with duplicate detection, dominant colours. JPEG, PNG, WebP, HEIC, AVIF. $1.50 per 1,000 images.

- **URL**: https://apify.com/gazidev/image-metadata-extractor.md
- **Developed by:** [Cemal Atakli](https://apify.com/gazidev) (community)
- **Categories:** Developer tools, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 image analyseds

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

## Image Metadata & EXIF Extractor – GPS, IPTC, AI Check, Hash

Read **all the metadata hidden in images**, in bulk, from a list of URLs. For every image you get **EXIF camera data** (make, model, lens, date taken, exposure, ISO, focal length), **GPS coordinates with the nearest city**, **IPTC and XMP** credits (creator, copyright, caption, keywords, usage terms), **AI-generation signals** (C2PA Content Credentials, IPTC "trained algorithmic media", Midjourney / DALL·E / Firefly / Stable Diffusion tags and prompts in PNG chunks), dimensions and format, **MD5/SHA-256 and perceptual hashes** with **duplicate detection**, and the **dominant colours**.

- **$1.50 per 1,000 images**, a third of the price of the most-used image metadata Actor on the Store. Broken links, non-images and oversized files are free.
- **Every common format:** JPEG, PNG, WebP, **HEIC/HEIF (iPhone)**, AVIF, TIFF, GIF and BMP.
- **Privacy audit built in:** `privacyFlags` tells you which images leak a GPS location, a camera serial number or an author name before you publish them.
- **No external APIs:** the nearest-city lookup runs offline on GeoNames data; no Google, no API key, no browser.

### What can I use it for?

- **Photo and asset audits:** find images on your site or in your DAM that still contain GPS or author data.
- **Rights and licensing checks:** pull the creator, copyright notice, credit line and usage terms from stock or agency images.
- **AI-content screening:** flag images that declare themselves AI-generated or carry C2PA Content Credentials (useful for marketplaces, newsrooms and UGC moderation).
- **Duplicate detection:** find exact copies and resized or re-compressed near-duplicates in a product catalogue or image dataset.
- **OSINT and journalism:** read when and where a photo was taken (when the metadata survived).
- **Dataset building:** dimensions, format, colours and hashes for ML or e-commerce image pipelines. Pair it with our [Image Downloader](https://apify.com/gazidev/image-downloader) to collect images from web pages first.

### Input

| Field | Description |
|---|---|
| `imageUrls`, `bulkText`, `sourceFileUrl`, `datasetId` | Image links: a list, pasted text/CSV, a remote file, or another Apify dataset |
| `reverseGeocode` | Nearest city, region and country for GPS photos (offline, default on) |
| `computeHashes`, `duplicateThreshold` | Perceptual hashes and duplicate detection (default on, 6 bits) |
| `extractColors` | Dominant and average colour (default on) |
| `includeRawExif` | All raw EXIF tags in addition to the summary (default on) |
| `onlyWithGps` | Output only photos that contain a location |
| `maxImages`, `maxFileSizeMb`, `maxConcurrency`, `proxyConfiguration` | Limits and performance |

```json
{
  "imageUrls": [
    "https://raw.githubusercontent.com/ianare/exif-samples/master/jpg/gps/DSCN0010.jpg",
    "https://iptc.org/std/photometadata/examples/IPTC-PhotometadataRef-Std2021.1.jpg"
  ]
}
```

### Output

One row per image. The Output tab has three tables: **Overview**, **Camera & GPS**, **Credits, rights & AI signals**.

```json
{
  "url": "https://raw.githubusercontent.com/ianare/exif-samples/master/jpg/gps/DSCN0010.jpg",
  "status": "ok",
  "format": "JPEG",
  "width": 640, "height": 480, "megapixels": 0.31,
  "fileSizeBytes": 161713,
  "camera": {
    "make": "NIKON", "model": "COOLPIX P6000", "dateTaken": "2008-10-22T16:28:39",
    "exposureTime": 0.0133, "fNumber": 5.9, "iso": 64, "focalLengthMm": 24.0,
    "software": "Nikon Transfer 1.1 W"
  },
  "gps": {
    "latitude": 43.467448, "longitude": 11.885127,
    "nearestCity": "Arezzo", "region": "Tuscany", "countryCode": "IT", "distanceKm": 0.6,
    "mapsUrl": "https://www.openstreetmap.org/?mlat=43.467448&mlon=11.885127#map=15/43.467448/11.885127"
  },
  "hasGps": true,
  "iptc": null,
  "xmp": null,
  "aiGenerationSignals": [],
  "aiGeneratedLikely": false,
  "hasC2pa": false,
  "privacyFlags": ["GPS location"],
  "md5": "97fdc6ae077d8165f3cb4aa494ddb7d4",
  "dHash": "313c1d66e2e4e595", "pHash": "cedbd88c49eaf808",
  "exactDuplicateOf": null, "nearDuplicateOf": null,
  "dominantColors": [{"hex": "#877142", "share": 0.251}, {"hex": "#6a512d", "share": 0.243}],
  "averageColor": "#98844d"
}
```

An IPTC-tagged image adds for example `"iptc": {"objectName": "The Title", "keywords": ["…"], "byline": "Creator1", "copyrightNotice": "…"}` and `"xmp": {"creator": "…", "rights": "…", "usageTerms": "…"}`. An AI-generated PNG returns signals such as `"IPTC DigitalSourceType: trainedAlgorithmicMedia"`, `"Generator software: Adobe Firefly"` or `"PNG 'parameters' chunk (Stable Diffusion / ComfyUI style generation data)"`, and the prompt itself in `textChunks`.

Errors are separate free rows with `errorCode`: `INVALID_URL`, `HTTP_404` (and other codes), `NOT_AN_IMAGE`, `TOO_LARGE`, `TIMEOUT`, `DECODE_ERROR`.

### Pricing

| Event | Price |
|---|---|
| Image analysed | **$0.0015** ($1.50 per 1,000) |
| Run start | $0.00005 |

| Actor | Monthly users | Price per 1,000 images |
|---|---|---|
| **This Actor** | – | **$1.50** (EXIF + GPS + IPTC/XMP + AI signals + hashes + colours) |
| perryay/image-metadata-analyzer | 27 | $5.00 |
| eliai/image-metadata-exif-extractor | 1 | $3.00 |
| sian.agency/exif-gps-metadata-extractor | 5 | $1.00 (EXIF/GPS only) |

Competitor figures are from the Apify Store on 2026-10-06.

### FAQ

**Why do many web images have no EXIF or GPS?** Most social networks, CMSs and CDNs strip metadata when they resize images. Original files from cameras and phones, stock agencies and many news sites keep it.

**Can it prove that an image is AI-generated?** No tool can from metadata alone. The Actor reports what the file *declares* (IPTC source type, C2PA manifest, generator software, embedded prompts). An image without these signals may still be AI-made, and metadata can be removed or forged.

**How does duplicate detection work?** Exact copies share the same SHA-256. Near-duplicates (resized, re-compressed or slightly edited copies) have a dHash within `duplicateThreshold` bits of an image analysed earlier in the same run; the row then points to it in `nearDuplicateOf`.

**Where does the nearest city come from?** From the GeoNames cities15000 dataset (CC-BY 4.0, geonames.org), bundled with the Actor. Coordinates never leave the run.

**Is this legal?** The Actor downloads only the image URLs you provide, sends an honest User-Agent and reads the metadata embedded in those files. Make sure you are allowed to process the images and be careful with personal data such as locations. Questions: iletisim.ortalamabiri@gmail.com.

### Use with AI agents / Apify MCP

Ask *"where and with which camera was this photo taken?"* or *"do any of these product images contain GPS or AI-generation tags?"*. Through the [Apify MCP server](https://mcp.apify.com) (`https://mcp.apify.com?actors=gazidev/image-metadata-extractor`), Claude, ChatGPT, Cursor and other MCP clients can call it directly. Over the API: `POST https://api.apify.com/v2/acts/gazidev~image-metadata-extractor/run-sync-get-dataset-items`.

### Related Actors

[Image Downloader](https://apify.com/gazidev/image-downloader) · [Image OCR](https://apify.com/gazidev/image-ocr) · [Video Frame Extractor](https://apify.com/gazidev/video-frame-extractor) · [Website Screenshot](https://apify.com/gazidev/website-screenshot)

# Actor input Schema

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

Direct links to JPEG, PNG, WebP, HEIC/HEIF, AVIF, TIFF, GIF or BMP files. Duplicates are removed; invalid values are reported for free.

## `bulkText` (type: `string`):

One URL per line, or a CSV with a `url` / `image` column.

## `sourceFileUrl` (type: `string`):

Public .txt or .csv link (Google Sheets 'Publish to web → CSV' works).

## `datasetId` (type: `string`):

Chain after a scraper or our Image Downloader.

## `datasetField` (type: `string`):

Arrays of URLs are accepted too.

## `reverseGeocode` (type: `boolean`):

Offline lookup (GeoNames cities ≥15,000 people): nearest city, region, country and distance. No external API is called.

## `computeHashes` (type: `boolean`):

aHash, dHash and pHash, plus `exactDuplicateOf` / `nearDuplicateOf` within the run.

## `duplicateThreshold` (type: `integer`):

Max Hamming distance between dHashes to call two images near-duplicates (resized or re-compressed copies). 0 = identical hash only.

## `extractColors` (type: `boolean`):

5 dominant colours with their share, plus the average colour.

## `includeRawExif` (type: `boolean`):

Off = only the summarised `camera` and `gps` objects.

## `onlyWithGps` (type: `boolean`):

Photos without location are skipped and not charged.

## `maxImages` (type: `integer`):

0 = all.

## `maxFileSizeMb` (type: `integer`):

Larger files are skipped for free (TOO_LARGE).

## `maxConcurrency` (type: `integer`):

Max 4 at a time per host.

## `proxyConfiguration` (type: `object`):

Only for hosts that block datacenter IPs.

## Actor input object example

```json
{
  "imageUrls": [
    "https://raw.githubusercontent.com/ianare/exif-samples/master/jpg/gps/DSCN0010.jpg",
    "https://iptc.org/std/photometadata/examples/IPTC-PhotometadataRef-Std2021.1.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/a/a9/Example.jpg"
  ],
  "datasetField": "url",
  "reverseGeocode": true,
  "computeHashes": true,
  "duplicateThreshold": 6,
  "extractColors": true,
  "includeRawExif": true,
  "onlyWithGps": false,
  "maxImages": 0,
  "maxFileSizeMb": 30,
  "maxConcurrency": 8,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

No description

## `camera` (type: `string`):

No description

## `rights` (type: `string`):

No description

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

No description

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

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

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "imageUrls": [
        "https://raw.githubusercontent.com/ianare/exif-samples/master/jpg/gps/DSCN0010.jpg",
        "https://iptc.org/std/photometadata/examples/IPTC-PhotometadataRef-Std2021.1.jpg",
        "https://upload.wikimedia.org/wikipedia/commons/a/a9/Example.jpg"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("gazidev/image-metadata-extractor").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 = { "imageUrls": [
        "https://raw.githubusercontent.com/ianare/exif-samples/master/jpg/gps/DSCN0010.jpg",
        "https://iptc.org/std/photometadata/examples/IPTC-PhotometadataRef-Std2021.1.jpg",
        "https://upload.wikimedia.org/wikipedia/commons/a/a9/Example.jpg",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("gazidev/image-metadata-extractor").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 '{
  "imageUrls": [
    "https://raw.githubusercontent.com/ianare/exif-samples/master/jpg/gps/DSCN0010.jpg",
    "https://iptc.org/std/photometadata/examples/IPTC-PhotometadataRef-Std2021.1.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/a/a9/Example.jpg"
  ]
}' |
apify call gazidev/image-metadata-extractor --silent --output-dataset

```

## MCP server setup

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

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/I2Eqgh3fv6zXsa6ke/builds/zJIxGgOCe3uZ1gxbX/openapi.json
