Skip to main content

Unsplash Python SDK

PyPI version Python Versions Test License: MIT Ruff pre-commit

A modern, type-safe Python client for the Unsplash API. Built with Pydantic v2 for robust data validation and httpx for high-performance sync and async support.

✨ Features

  • Type Safe: Fully typed response models using Pydantic v2.
  • Async Native: First-class async/await support with AsyncUnsplashClient.
  • Modern: Built on httpx (HTTP/2 support, connection pooling).
  • Developer Friendly: IDE auto-completion, detailed error messages, and fully documented resources.
  • Resource Oriented: Clean API design mirroring the Unsplash documentation (Photos, Users, Collections, Search, Topics).

🛠️ Installation

Install usage pip:

pip install unsplash-pydantic

Or using Poetry:

poetry add unsplash-pydantic

🚀 Quick Start

Synchronous Client

Perfect for scripts and standard applications.

import os
from unsplash import UnsplashClient

# Initialize the client
client = UnsplashClient(access_key=os.getenv("UNSPLASH_ACCESS_KEY"))

# Get a random photo of nature
photo = client.photos.random(query="nature", orientation="landscape")

# Access typed fields
print(f"Photo by: {photo.user.name}")
print(f"Description: {photo.description}")
print(f"Download URL: {photo.urls.full}")

# Search for photos
results = client.search.photos("mountains", page=1, per_page=10)
print(f"Found {results.total} photos")

# Release the connection pool when you are done
client.close()

The client can also be used as a context manager, which closes the underlying connection pool on exit:

with UnsplashClient(access_key=os.getenv("UNSPLASH_ACCESS_KEY")) as client:
    photo = client.photos.random(query="nature")

Asynchronous Client

Ideal for high-concurrency applications (FastAPI, etc).

import asyncio
import os
from unsplash import AsyncUnsplashClient

async def main():
    async with AsyncUnsplashClient(access_key=os.getenv("UNSPLASH_ACCESS_KEY")) as client:
        # Fetch user profile asynchronously
        user = await client.users.get("ousplash")
        print(f"{user.name} has {user.total_photos} photos")

        # Get their latest photos
        photos = await client.users.photos(user.username, per_page=5)
        for photo in photos:
            print(f"- {photo.id}: {photo.urls.regular}")

if __name__ == "__main__":
    asyncio.run(main())

If you cannot use async with, call await client.aclose() to release the connection pool explicitly.

Topics

Topics are Unsplash's editorial categories. Accepted by id or slug:

# Browse topics (order_by: featured, latest, oldest, position)
for topic in client.topics.list(order_by="featured"):
    print(f"{topic.slug}: {topic.total_photos} photos")

# A single topic, and its photos
topic = client.topics.get("wallpapers")
photos = client.topics.photos("wallpapers", orientation="landscape", per_page=5)

Statistics

photo_stats = client.photos.statistics("Dwu85P9SOIk", resolution="days", quantity=30)
print(photo_stats.downloads.total)

user_stats = client.users.statistics("exampleuser")
platform = client.stats.total()      # totals across all of Unsplash
last_month = client.stats.month()    # trailing 30 days

All statistics fields are optional, so check for None before use.

📚 Core Concepts

Error Handling

All specific errors catch a base UnsplashError. Common HTTP errors (401, 404, 429) are mapped to specific exceptions.

from unsplash import UnsplashClient, UnsplashError, RateLimitError

try:
    client.photos.get("invalid-id")
except RateLimitError as e:
    print(f"Rate limited! Limit: {e.limit}, Remaining: {e.remaining}")
except UnsplashError as e:
    print(f"API Error: {e.message}")

Optional Fields

Unsplash returns abbreviated objects when a resource is embedded in another one. A user nested inside a photo, for example, omits profile_image and most total_* counters, and its links may carry only self, html and photos.

The models mirror that reality: only fields present in every representation are required. On User that is id and username; on Photo it is id, created_at, width, height, urls, links and user. Everything else is Optional and defaults to None.

photo = client.photos.get("Dwu85P9SOIk")

photo.urls.full          # always present
photo.user.username      # always present

if photo.user.profile_image:          # may be omitted on an embedded user
    print(photo.user.profile_image.large)

This means a type checker will point at the None cases for you, instead of the client raising a ValidationError from deep inside a response you cannot see.

Retries

Transport errors (connection resets, DNS failures, timeouts) and 5xx responses are retried automatically with exponential backoff, up to max_retries times (default 3, so up to 4 attempts total). A Retry-After header is honored when the server sends one.

Rate limits (429) are deliberately not retried unless the response carries a short Retry-After. Unsplash's quota resets hourly, so retrying a rate-limited request would only delay the RateLimitError you need to handle:

from unsplash import RateLimitError, UnsplashClient

# Disable retries entirely
client = UnsplashClient(access_key="...", max_retries=0)

try:
    photo = client.photos.random()
except RateLimitError as exc:
    print(f"Quota exhausted: {exc.remaining}/{exc.limit} remaining")

Unsplash Guidelines

This SDK helps you follow Unsplash API Guidelines:

  1. Attribution: The Photo model includes the user object with name and links to properly credit photographers.
  2. Download Tracking: Use client.photos.track_download(id) or client.photos.download(id) to trigger the download event required by the API.
  3. Hotlinking: photo.urls provides hotlinkable URLs directly.

🤝 Contributing

We welcome contributions! Please see our Contributing Guide for details.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.


This library is not officially affiliated with Unsplash.

Metadata

Release files for unsplash-pydantic 0.4.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for unsplash-pydantic 0.4.1
File Size Uploaded
unsplash_pydantic-0.4.1.tar.gz 16.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for unsplash-pydantic 0.4.1
File Interpreter ABI Platform
unsplash_pydantic-0.4.1-py3-none-any.whl Python 3 none any Details

Total release size: 37.5 kB

Release files / unsplash_pydantic-0.4.1.tar.gz

Download URL unsplash_pydantic-0.4.1.tar.gz
Size 16.7 kB
Tags Source
SHA-256 checksum
How to use checksums
34b0d411cedbd8a45977feb23c94f33e10016b9042eabe0bd56cd94aa9300d77
BLAKE2b-256 checksum
How to use checksums
1a692449744ba6556c21fc212876f6d416811a949da0e2c96b3adbe451deebab
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.

Transparency log

Release files / unsplash_pydantic-0.4.1-py3-none-any.whl

Download URL unsplash_pydantic-0.4.1-py3-none-any.whl
Size 20.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e4ff3d6b62a07a93a39a990c579ffb5f88c570b45a5220b2adf82304d328b68d
BLAKE2b-256 checksum
How to use checksums
e9baeeabc8e2cef37f0d34cb4f3c902f001b6f08a22a223fe6f808d554881f97
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.1 This release

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page