GuidesIMAGE APIS
Free image and stock photo APIs for a website
We called 12 free image APIs from Chromium and Firefox on 26 October 2026: all 11 with a JSON API send CORS headers, so call them directly. An img tag needs no CORS at all. CORS only matters when you fetch() the bytes or read canvas pixels, which worked on 8 image hosts and failed on The Met and The Cat API. Unsplash, Pexels and Pixabay need a key you should keep on a server.
Results by API
Each image went through four checks in Chromium 156 and Firefox 157: a plain img tag, a canvas read with getImageData, the same read with crossOrigin = "anonymous", and fetch(). Keyed JSON endpoints got a fake key: a 401 that carries Access-Control-Allow-Origin proves the browser can read the reply.
| API | Key | CORS on JSON | CORS on image files | Free limit | License and credit |
|---|---|---|---|---|---|
| Unsplash | Access Key, Client-ID header | Yes | Yes, canvas works | 50 an hour in demo, 1,000 in production | Credit photographer and Unsplash, hotlink |
| Pexels | Key in Authorization header | Yes | Yes, canvas works | 200 an hour, 20,000 a month | Credit photographers when possible |
| Pixabay | key= in the URL | Yes | Yes, canvas works | 100 per 60 seconds, cache 24 hours | Show the source, no permanent hotlinking |
| Openverse | None | Yes, about 9 s uncached | Thumbnails yes, originals vary | 20 a minute, 200 a day anonymous | CC license or public domain per result |
| Wikimedia Commons | None, add origin=* | Only with origin=* | Yes on thumbnails | No hard read limit, go serial | Per file, in extmetadata |
| NASA APOD | api_key=, DEMO_KEY | Yes | Untested, API returned a logo | 1,000 an hour with a free key | Credit in the copyright field |
| Lorem Picsum | None | Yes | Yes, canvas works | None published | Unsplash photos, author in /v2/list |
| placehold.co | None | No JSON, image URLs only | Yes, SVG by default | None published | Generated placeholder images |
| Dog CEO | None | Yes | Yes, canvas works | None published | Open source dog photos |
| The Cat API | x-api-key, search works without | Yes | No header, canvas fails | 10,000 a month on the free plan | Commercial license from $99 a month |
| Art Institute of Chicago | None | Yes | Yes on IIIF, behind a bot check | 60 a minute per IP | Filter is_public_domain=true |
| The Met | None | Yes | No header, canvas fails | 80 requests a second | Public domain when isPublicDomain |
The Met and The Cat API image hosts send no CORS header, so their images display but cannot be read. The Art Institute of Chicago image server sent Access-Control-Allow-Origin: * to curl, but its Cloudflare bot check blocked our automated browsers, so test it in your own.
Show images with img, read pixels with crossOrigin
An img tag displays cross-origin images with no CORS headers. CORS matters once you draw the image on a canvas and read it back. Drawing a plain img and calling getImageData threw a SecurityError on every host we tried, even hosts that send Access-Control-Allow-Origin, because the browser only checks that header when the image is requested in CORS mode.
Set crossOrigin = "anonymous" before src and the read works on hosts that send the header: Unsplash, Pexels, Pixabay, Openverse, Wikimedia Commons, Lorem Picsum, placehold.co and Dog CEO. Do not add it to every image by default. On The Met and The Cat API hosts, crossOrigin made the image fail to load at all, and fetch() failed with "Failed to fetch". Leave it off when you only display the image.
const image = new Image();
image.crossOrigin = 'anonymous';
image.src = 'https://picsum.photos/seed/cors/400/300';
await image.decode();
const canvas = document.createElement('canvas');
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
const context = canvas.getContext('2d');
context.drawImage(image, 0, 0);
// Without crossOrigin, or without Access-Control-Allow-Origin on the image, this throws a SecurityError
const [red, green, blue] = context.getImageData(0, 0, 1, 1).data;
console.log({ red, green, blue }, canvas.toDataURL('image/jpeg').length);The tainted canvas guide covers this error in depth.
Search images without a key
Openverse searches Creative Commons and public-domain images, and each result carries its license and a ready-made attribution line. Its response headers give anonymous callers 20 requests a minute and 200 a day; anonymous page_size stops at 20 and pagination at 240 results.
Wikimedia Commons needs origin=* in the query string. With it, the API sends Access-Control-Allow-Origin: * and treats the request as logged out; without it, there is no CORS header. There is no hard read limit, but Wikimedia asks for requests one after another, not in parallel. Each file's license comes back in extmetadata.
const params = new URLSearchParams({
action: 'query',
generator: 'search',
gsrsearch: 'lighthouse filetype:bitmap',
gsrnamespace: '6',
gsrlimit: '12',
prop: 'imageinfo',
iiprop: 'url|extmetadata',
iiurlwidth: '640',
iiextmetadatafilter: 'LicenseShortName',
format: 'json',
origin: '*',
});
const data = await fetch(`https://commons.wikimedia.org/w/api.php?${params}`).then((response) => response.json());
const images = Object.values(data.query?.pages ?? {}).map((page) => ({
src: page.imageinfo[0].thumburl,
page: page.imageinfo[0].descriptionurl,
license: page.imageinfo[0].extmetadata?.LicenseShortName?.value,
}));
console.table(images);Wikipedia takes the same parameter; see Wikipedia API from JavaScript. Two museum APIs also search with no key:
- Art Institute of Chicago: 60 requests a minute per IP. Add
query[term][is_public_domain]=trueand build image URLs from each result'simage_idon its IIIF server. - The Met: stay under 80 requests a second. The old
/public/collection/v1/searchreturns 410 Gone since 1 October 2026;/public/collection/v1.1/searchpages withoffsetandlimitand returns 100 results by default. Use objects withisPublicDomain: true.
Unsplash, Pexels and Pixabay
All three send CORS headers on JSON and on image files, and all three need a key. Any visitor can copy a key from frontend code and burn your quota. Keep it in a small server function that returns only what the page needs.
- Unsplash:
Authorization: Client-ID KEY, 50 requests an hour in demo mode and 1,000 after production approval. Onlyapi.unsplash.comcalls count, not image loads. Hotlink the returnedphoto.urls, callphoto.links.download_locationwhen a user picks a photo, and credit the photographer and Unsplash with links that carryutm_sourceandutm_medium=referral. - Pexels: key in the
Authorizationheader, 200 requests an hour and 20,000 a month, raised for free if you meet its terms. Credit photographers when possible. - Pixabay:
key=in the URL, 100 requests per 60 seconds. Cache responses for 24 hours and show where images come from. Search results may hotlink for a while, but images you keep must be downloaded to your own server.
export default {
async fetch(request, env) {
const query = new URL(request.url).searchParams.get('q') ?? 'cat';
const upstream = await fetch(
`https://api.unsplash.com/search/photos?per_page=12&query=${encodeURIComponent(query)}`,
{ headers: { Authorization: `Client-ID ${env.UNSPLASH_ACCESS_KEY}`, 'Accept-Version': 'v1' } },
);
const data = await upstream.json();
// We send the browser only what it needs to show and credit each photo
const body = upstream.ok
? data.results.map((photo) => ({
src: photo.urls.small,
alt: photo.alt_description,
author: photo.user.name,
profile: `${photo.user.links.html}?utm_source=your_app&utm_medium=referral`,
downloadLocation: photo.links.download_location,
}))
: { error: data.errors?.join(', ') ?? `HTTP ${upstream.status}` };
return Response.json(body, {
status: upstream.status,
headers: { 'Access-Control-Allow-Origin': 'https://your-site.example' },
});
},
};No backend yet? API proxy without a server shows where a function like this can live.
Placeholders, pets and NASA APOD
- Lorem Picsum: Unsplash photos at URLs like
picsum.photos/200/300,/id/237/200/300and/seed/NAME/200/300, with?grayscaleand?blur=1to10./v2/listgives the author. - placehold.co: image URLs only, SVG by default, also PNG, JPEG, GIF, WebP and AVIF, up to 4000 x 4000 pixels.
- Dog CEO: random dog photos, with CORS on the JSON and on the images.
- The Cat API: search works without a key. The free plan is 10,000 requests a month with an
x-api-keyheader, and a commercial license starts at $99 a month.
NASA's APOD API is broken today. Without a date it returned HTTP 500. With a date it returned 200, but every date we tried came back titled "NASA Science" with the NASA logo as the image. The docs give DEMO_KEY 30 requests an hour and 50 a day per IP, yet its header said 10, and the 11th call got 429 with a Retry-After of about 20 hours. For a daily photo, the Bing image of the day works through the free proxy.
Where the cors.dev proxy fits
You do not need a proxy for the JSON APIs on this page. Openverse and the Art Institute answered the free proxy with a Cloudflare challenge anyway, so call them directly. The free tier also refuses keys in the URL, such as Pixabay's key= or NASA's api_key=, with 400 and X-Cors-Error: credential_in_url: a private key should not go through a public relay.
The free tier relays text and JSON only. Every raster image we sent returned 502 with X-Cors-Error: unsupported_response; a placehold.co SVG passed because SVG is text. If you must read pixels from The Met or The Cat API images, cors.dev Pro ($5 a month) relays images up to 6 MiB with CORS headers.
Good questions.
Do I need CORS to show images from an API?
No. An img tag displays cross-origin images without CORS headers. You need CORS on the image file only to read its bytes with fetch() or its pixels from a canvas.
Which free image API needs no key?
Openverse, Wikimedia Commons with origin=*, the Art Institute of Chicago and The Met all search images with no key. Lorem Picsum, placehold.co, Dog CEO and the basic search of The Cat API need none either.
Can I put my Unsplash access key in frontend code?
No. Any visitor can copy it and use up your 50 requests an hour in demo mode, or 1,000 in production. Unsplash's guidelines say the Access Key must remain confidential, so call the API from a server function.
Why does getImageData throw a SecurityError?
The canvas is tainted: the image was drawn without a CORS request, even if its server sends Access-Control-Allow-Origin. Set crossOrigin = "anonymous" before src on a host that sends the header; the tainted canvas guide has the details.
Does the NASA APOD API still work?
Not on 26 October 2026. Requests without a date returned HTTP 500, and dated requests returned the NASA logo instead of the photo. DEMO_KEY also allowed only 10 calls before a 429.
Can I hotlink Pixabay images?
Only to show search results for a while. Pixabay does not allow permanent hotlinking, so download the images you keep to your own server and show where they came from.