Image Metadata API: EXIF Reader, GPS Off by Default avatar

Image Metadata API: EXIF Reader, GPS Off by Default

Pricing

from $1.40 / 1,000 images

Go to Apify Store
Image Metadata API: EXIF Reader, GPS Off by Default

Image Metadata API: EXIF Reader, GPS Off by Default

Get image metadata from public image URLs: format, size, orientation, DPI, colour space, camera, lens, exposure, ISO, focal length and capture time. GPS and personal fields are off by default. Independent tool, not affiliated with any image host.

Pricing

from $1.40 / 1,000 images

Rating

0.0

(0)

Developer

Don Mangu

Don Mangu

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

8 days ago

Last modified

Categories

Share

Image metadata API: read technical EXIF data from public image URLs, with GPS and personal fields off by default. $2.00 per 1,000 images ($0.002 per image), no API key.

This Actor reads the image metadata of any list of public image links and returns one flat row per image: file format, width, height, orientation, colour space, DPI, camera make and model, lens, exposure time, aperture, ISO, focal length, flash, capture time and software. It downloads only the first bytes of each file (the header), so a 4 MB phone photo costs about 30 KB of traffic. It never changes, converts or stores your images.

Image metadata without personal data

Photos carry more than camera settings. The same EXIF block can hold GPS coordinates, the photographer's name, a copyright holder, camera and lens serial numbers, owner names, free-text comments and a small embedded thumbnail. Most buyers of an image metadata API want the technical part for catalogues, quality checks, asset audits and AI data sets, and do not want to store the rest.

So this Actor works from a fixed list of technical tags and leaves everything else unread:

  • Returned by default: format, width, height, orientation, colour space, DPI, camera make and model, lens make and model, exposure time, f-number, ISO, focal length, flash, capture time, software.
  • Dropped by default and never read: GPS coordinates, artist, copyright holder, owner name, serial numbers, unique image IDs, maker notes, user comments, image descriptions, XMP and IPTC text, embedded thumbnails.
  • Opt-in: set includeGps (the "Include GPS location" field) to true to add latitude, longitude and altitude.

Why GPS is off by default: a location in a photo can show where a person lives, works or spends their time. The GDPR text on personal data (Article 4, read on gdpr-info.eu on 9 October 2026) lists "location data" among the identifiers: "in particular by reference to an identifier such as a name, an identification number, location data,". A dataset full of coordinates is therefore personal data in many jurisdictions. With the switch off, your dataset holds no coordinates. The row still has hasGps, which says true or false, so you can find images that leak a location without storing where.

How to read image metadata, step by step

  1. Open the Actor and go to the Input tab.
  2. Paste one public image link per line into Image URLs (imageUrls). The prefill holds two sample photos.
  3. Leave Include GPS location (includeGps) off unless you have the right to process locations.
  4. Set Max images (maxImages) to cap the run. The prefill is 25.
  5. Click Start. Each row appears in the dataset as soon as the image is read.
  6. Open the Output tab for the table view, or download JSON, CSV or Excel. The key-value store record STATS shows requests, bytes and statuses.

You can also call the Actor from the Apify API or from an MCP client with the same input JSON.

Bulk lists

For a long list, you do not have to paste every link into the form. You can give the Actor a Dataset or a file, or use all three together.

  1. Dataset: pick a Dataset in Dataset with the list (listDatasetId). Each item holds one image link. Type the name of the field in Field in the dataset (listDatasetField), for example imageUrl. If you leave it empty, the Actor takes the first text field it finds in each item, and looks for url and imageUrl first.
  2. File: paste the web address of a .txt, .csv or .json file in List file URL (listFileUrl). A .txt file has one link per line. In a .csv file the first column is the link. A .json file is an array of links, or an array of objects that hold a url. A header row is skipped. The file can be up to 10 MB and must be on a public web address.
  3. Form: the links typed in Image URLs are still read. The Actor reads the typed links first, then the Dataset, then the file.

The Actor joins the three lists into one and removes repeated links (links are compared without regard to upper or lower case). Max images (maxImages) and the limit of 10,000 links apply to the joined list, and the first links in that order are read. The price stays the same: each image that was read and saved with resultStatus ok is one Image event, and links that fail are free. If the Dataset or the file cannot be read, the run stops before any request with a message, and nothing is charged.

Large lists: the Actor reads images in small groups of six and saves each row as soon as it is ready, so results do not pile up in memory. In a local test with a Dataset of 10,000 links and Max images set to 12, the Actor read only 12 images and its peak memory was 129 MB, which fits the default 256 MB. Speed is limited by the polite rate per host (see the FAQ). In a local test against one host, 40 images took 11.6 seconds, which is about 3.4 images per second. At that rate, 1,000 images from one host take about 294 seconds (1,000 / 3.4). The platform speed was not measured. If a run moves to another server, it continues from the list it resolved at the start, so a changed Dataset or file does not shift the rows.

Pricing

Pay per event, with one event: Image. You pay for each image that was read and saved with resultStatus ok. Links that fail (404, not an image, blocked address, timeout) are saved with a reason and cost nothing. The standard Actor start event of $0.00005 applies per run.

PlanPrice per imagePrice per 1,000 images
Free$0.002$2.00
Bronze$0.0018$1.80
Silver$0.0016$1.60
Gold$0.0014$1.40

Worked example on the Free plan: 25 images = 25 x $0.002 = $0.05, plus $0.00005 for the start, so $0.05005 in total. 1,000 images = 1,000 x $0.002 = $2.00 plus $0.00005. If 40 of your 1,000 links are broken, you pay for 960 images: 960 x $0.002 = $1.92.

The Actor stops cleanly when your maximum charge per run is reached.

Input example

{
"imageUrls": [
"https://raw.githubusercontent.com/drewnoakes/metadata-extractor-images/master/jpg/Nikon%20D70.jpg",
"https://raw.githubusercontent.com/drewnoakes/metadata-extractor-images/master/jpg/Apple%20iPhone%204.jpg"
],
"maxImages": 25
}
FieldKeyMeaning
Image URLsimageUrlsPublic image links, one per line. Up to 10,000.
Dataset with the listlistDatasetIdOptional. A Dataset that holds image links, one per item. Added to the links typed above.
Field in the datasetlistDatasetFieldOptional. The Dataset field that holds the link. Empty means the first text field.
List file URLlistFileUrlOptional. Web address of a .txt, .csv or .json file with the links. Up to 10 MB.
Include GPS locationincludeGpsDefault false. When true, adds gpsLatitude, gpsLongitude, gpsAltitudeMeters.
Max imagesmaxImagesDefault 100. Links beyond this number are skipped.
Maximum bytes to read per imagemaxBytesPerImageDefault 524,288 (512 KB). Range 16,384 to 8,388,608.

Output example

Real output of the Actor run on 9 October 2026 against a public sample photo (Nikon D70, from the drewnoakes/metadata-extractor-images repository). The server held a 1,602,061 byte file; the Actor read the first 49,152 bytes.

{
"inputUrl": "https://raw.githubusercontent.com/drewnoakes/metadata-extractor-images/master/jpg/Nikon%20D70.jpg",
"resultStatus": "ok",
"errorMessage": null,
"format": "JPEG",
"width": 3008,
"height": 2000,
"orientation": 1,
"orientationLabel": "Horizontal (normal)",
"colorSpace": "sRGB",
"dpiX": 300,
"dpiY": 300,
"hasExif": true,
"cameraMake": "NIKON CORPORATION",
"cameraModel": "NIKON D70",
"lensMake": null,
"lensModel": null,
"exposureTime": "1/60",
"exposureSeconds": 0.016667,
"fNumber": 4.5,
"iso": null,
"focalLengthMm": 70,
"focalLength35mm": 105,
"flashFired": true,
"capturedAt": "2004-11-21T21:29:12",
"capturedAtOffset": null,
"software": "Nikon Transfer 6.2.1 W",
"hasGps": false,
"gpsLatitude": null,
"gpsLongitude": null,
"gpsAltitudeMeters": null,
"fileBytes": 1602061,
"bytesRead": 49152,
"readComplete": true,
"retrievedAt": "2026-10-09T13:59:41.260Z"
}

With includeGps true, the fields gpsLatitude, gpsLongitude and gpsAltitudeMeters are filled when the image has GPS tags. Real output for a public iPhone 4 sample photo (other fields shortened):

{
"inputUrl": "https://raw.githubusercontent.com/drewnoakes/metadata-extractor-images/master/jpg/Apple%20iPhone%204.jpg",
"cameraMake": "Apple",
"cameraModel": "iPhone 4",
"hasGps": true,
"gpsLatitude": 41.853,
"gpsLongitude": 12.488833
}

A link that fails gives a free row with a reason. Real output for a missing file:

{
"inputUrl": "https://raw.githubusercontent.com/MikeKovarik/exifr/master/does-not-exist.jpg",
"resultStatus": "http_error",
"errorMessage": "The server answered HTTP 404."
}

Output fields

Every field below is in the dataset for every row (empty when the file does not state it): inputUrl, resultStatus, errorMessage, format, width, height, orientation, orientationLabel, colorSpace, dpiX, dpiY, hasExif, cameraMake, cameraModel, lensMake, lensModel, exposureTime, exposureSeconds, fNumber, iso, focalLengthMm, focalLength35mm, flashFired, capturedAt, capturedAtOffset, software, hasGps, gpsLatitude, gpsLongitude, gpsAltitudeMeters, fileBytes, bytesRead, readComplete, retrievedAt.

How this differs from our other image Actors

  • Bulk Image Converter & Compressor converts and compresses images and can strip EXIF and GPS from the output files. This Actor changes nothing: it only reads the metadata and returns it as rows.
  • Bulk Image Downloader API finds images on web pages and saves them in a ZIP; its rows hold link, format, size, dimensions and alt text. It downloads whole files. This Actor takes a list of image links, reads only the header, and adds camera, lens, exposure, ISO, capture time, DPI and orientation. Both report width and height; only this Actor reads EXIF.
  • Image to Text OCR reads text printed in the picture. This Actor never looks at pixels.
  • AI Image Upscaler enlarges images. Use this Actor first to check the size and DPI of a batch.

Also useful: Website Screenshot API.

FAQ

You send the links, so you decide which images are read. The Actor requests only public files that anyone can open with a browser, sends no login and uses no cookies. The EXIF reader is the open-source library exifr (npm), under the MIT licence: "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction". Metadata that names people or places is personal data in many countries. That is why those fields are dropped by default. If you switch on includeGps, you are responsible for a legal basis to process the locations. This is not legal advice.

Does it read private or logged-in images?

No. Links to private networks, local addresses and cloud metadata addresses are blocked, and links with a user name or password are refused. A link that needs a login returns an http_error row.

What request rate does it use?

Per host, at most 2 requests run at the same time and request starts are spaced 250 ms apart (up to 4 per second). The Actor honours the Retry-After header on HTTP 429 and 5xx answers (up to 30 seconds), and tries each link up to 3 times with growing waits. It sends a Range request for the first bytes and follows up to 5 redirects. It declares itself with the User-Agent ImageMetadataApi-bot/1.0. It uses no proxies and does not try to get around blocks.

Which formats work?

JPEG, PNG, GIF, WebP, TIFF, BMP, HEIC and AVIF. EXIF is read from JPEG, PNG (eXIf chunk), WebP, TIFF and HEIC. GIF and BMP give format, size and (for BMP) DPI. AVIF is read like HEIC; the AVIF test file had no EXIF, so AVIF EXIF reading is untested. SVG is unsupported and RAW camera files are untested.

Why is a field empty?

The file does not state it. Many images carry no EXIF at all (screenshots, web images). hasExif tells you which case you have. For example the Nikon D70 sample has no ISO tag, because that camera writes ISO into its maker note, which this Actor does not read.

Why did I get limit_reached?

The size or EXIF block of that file lies beyond maxBytesPerImage. JPEG and PNG headers end after a few kilobytes. WebP, TIFF and HEIC can keep metadata deeper in the file. Raise Maximum bytes to read per image. A row with readComplete false was read up to the limit and may hide metadata.

Why does the capture time have no time zone?

Cameras write local clock time without a zone. The Actor returns that text as written. If the file has an offset tag, it appears in capturedAtOffset. The output does not depend on the time zone of the server.

Which statuses can I get?

ok (charged), invalid_url, blocked_address, http_error, fetch_failed, not_an_image, damaged_image, limit_reached (all free).

Colour space says Uncalibrated for my iPhone photo. Is that wrong?

The Actor reports what the EXIF colour space tag says. The two public iPhone HEIC samples we tested carry the value 65535, which means Uncalibrated. The Actor does not read ICC profiles.

Disclaimer

This is an independent tool by Don Mangu. It is not affiliated with, endorsed by or connected to any image host, camera maker or to the authors of exifr. Camera and software names in the output are values read from your files.