WrapSplash++ is a simple, synchronous API wrapper for the popular Unsplash platform. The library is written in C++17, built with CMake, and can be used in any C++ project. This is a C++ port of the popular wrapsplash TypeScript library. Unsplash provides beautiful high quality free images and photos that you can download and use for any project without any attribution.
Before using the Unsplash API, you need to register as a developer and read the API Guidelines.
Note: Every application must abide by the API Guidelines. Specifically, remember to hotlink images and trigger a download when appropriate.
- About
- Installation
- Sample Usage
- Development
- Docker
- New API aliases
- Dependency
- Changelog
- API Documentation
- Schema
- Authorization
- Users APIs
- Photos APIs
- Search APIs
- Current User APIs
- Stats APIs
- Collections APIs
- Link Relations
- List Collections
- List Featured Collections
- List Curated Collections
- Get a Collection
- Get a Curated Collection
- Get a Collection's Photos
- Get a Curated Collection's Photos
- List a Collection's Related Collections
- Create a New Collection
- Update an Existing Collection
- Delete a Collection
- Add a Photo to a Collection
- Remove a Photo from a Collection
- Continuous Integration (CI)
- Tests
- License
- Acknowledgements
Install the package using vcpkg:
vcpkg install wrapsplash++Or using Conan:
conan install wrapsplash++/1.0.0@Or build from source using CMake:
git clone https://github.com/Sandeepv68/wrapsplash-cpp.git
cd wrapsplash-cpp
mkdir build && cd build
cmake ..
cmake --build .
cmake --install .#include <wrapsplash/wrapsplash.hpp>
#include <iostream>
int main() {
wrapsplash::WrapSplash api;
api.init({
.bearer_token = "<bearer-token>"
});
try {
auto result = api.get_photo("<photo-id>");
std::cout << result.dump(2) << std::endl;
} catch (const wrapsplash::WrapSplashError& e) {
std::cerr << e.what() << std::endl;
}
return 0;
}mkdir build && cd build
cmake -DWRAPSLASH_BUILD_TESTS=ON ..
cmake --build .
ctestYou can build and test the project entirely inside Docker, which avoids installing dependencies on your host machine.
Prerequisites: Docker Desktop must be running.
Build the project:
docker compose run buildRun the tests:
docker compose run testBuild and test in one shot:
docker compose up build testOn subsequent runs, Docker caches the dependency layer and preserves compiled objects in a named volume (build-cache), so only changed source files are recompiled.
How it works:
Dockerfileinstalls all build dependencies (cmake, g++, curl, nlohmann-json, openssl, googletest) on Ubuntu 24.04.docker-compose.ymlmounts your source code into the container and uses abuild-cachevolume for the build directory, enabling incremental builds..dockerignorekeeps the Docker build context small by excludingbuild/,.git/, IDE files, etc.
The library includes more descriptive convenience methods such as get_photo, get_random_photo, create_collection, and update_collection. The original method names remain available for backward compatibility.
// Using the new aliases
api.create_collection("My Collection", "Description", false);
// Using the original method names (also works)
api.create_new_collection("My Collection", "Description", false);This library depends on libcurl for HTTP requests, nlohmann/json for JSON handling, and OpenSSL for SHA256 request header generation for the Unsplash API.
- Initial C++ port of wrapsplash TypeScript library
- Synchronous API with exception-based error handling
- C++17 with
std::optionalfor optional parameters - CMake build system with vcpkg and Conan package support
- Automatic retry with configurable timeout
- SHA256 request header verification using OpenSSL
- Complete Unsplash API coverage (Users, Photos, Search, Collections, Stats, OAuth)
- GoogleTest unit test suite
- Cross-platform support (Linux, macOS, Windows)
The API we are using is https://api.unsplash.com/. Responses are sent as JSON.
When retrieving a list of objects, an abbreviated or summary version of that object is returned - i.e., a subset of its attributes. To get a full detailed version of that object, fetch it individually.
If an error occurs, whether on the server or client side, the error message(s) will be returned in an errors array.
For example:
422 Unprocessable Entity{
"errors": ["Username is missing", "Password cannot be blank"]
}Many actions can be performed without requiring authentication from a specific user. For example, downloading a photo does not require a user to log in.
To authenticate requests in this way, pass your application's access key via the HTTP Authorization header:
Authorization: Client-ID YOUR_ACCESS_KEYYou can also pass this value using a client_id query parameter:
https://api.unsplash.com/photos/?client_id=YOUR_ACCESS_KEYIf only your access key is sent, attempting to perform non-public actions that require user authorization will result in a 401 Unauthorized response.
The Unsplash API uses OAuth2 to authenticate and authorize Unsplash users. Unsplash's OAuth2 paths live at https://unsplash.com/oauth/.
Before using wrapsplash++:
- Developers are required to create a developer account from Unsplash.
- Create a new App from Your Apps page.
- Get the
Access Key,Secret key,Callback URLs, andAuthorization code. - If you have a Bearer Token, then its super, or else you can generate it using wrapsplash++.
Note:
Authorization codecan be obtained by clicking theAuthorizelink next toCallback URLs. AlsoAuthorization codeis a one-time use code, you have to generate it again, if the action fails!.
WrapSplash++ instance has to be initialized with your credentials obtained from Unsplash developer account for programatic access.These credentials information are passed in to the init() function as options. The following example shows all the available options.
api.init({
.access_key = "<api-key>",
.secret_key = "<secret-key>",
.redirect_uri = "<callback-url>",
.code = "<authorization-code>",
.bearer_token = "<bearer-token>"
});If you have a bearer_token, then only bearer token has to be passed in.
api.init({
.bearer_token = "<bearer-token>"
});A function to generate a Bearer Token for write_access to private data.
The init() method in this case requires access_key, secret_key, redirect_uri, and code to generate bearer token.
Note: No Parameters are required for this function.
#include <wrapsplash/wrapsplash.hpp>
wrapsplash::WrapSplash api;
api.init({
.access_key = "<api-key>",
.secret_key = "<secret-key>",
.redirect_uri = "<callback-url>",
.code = "<authorization-code>"
});
try {
auto result = api.generate_bearer_token();
std::cout << result.dump(2) << std::endl;
} catch (const wrapsplash::WrapSplashError& e) {
std::cerr << e.what() << std::endl;
}If successful, the response body will be a JSON representation of your user's access token a.k.a bearer token:
{
"access_token": "091343ce13c8ae780065ecb3b13dc903475dd22cb78a05503c2e0c69c5e98044",
"token_type": "bearer",
"scope": "public read_photos write_photos",
"created_at": 1436544465
}and once you have your bearer_token you can use it in your app like this:
api.init({
.bearer_token = "<bearer-token>"
});A function to retrieve public details on a given user.
GET /users/:username
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| username | string | The username of the particular user | no | |
| width | int | Width of the profile picture in pixels | yes | |
| height | int | Height of the profile picture in pixels | yes |
Note: When optional height & width are specified the profile image will be included in the "profile_image" object as "custom".
auto profile = api.get_public_profile("<username>", 600, 600);A function to retrieve a single user's portfolio link.
GET /users/:username/portfolio
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| username | string | The username of the particular user | no |
auto portfolio = api.get_user_portfolio("<username>");A function to get a list of photos uploaded by a particular user.
GET /users/:username/photos
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| username | string | The username of the particular user | no | |
| page | int | Page number to retrieve | yes | 1 |
| per_page | int | Number of items per page | yes | 10 |
| stats | bool | Show the stats for each user's photo | yes | false |
| resolution | string | The frequency of the stats | yes | days |
| quantity | int | The amount of for each stat | yes | 30 |
| order_by | string | How to sort the photos.(Valid values: latest, oldest, popular) |
yes | latest |
auto photos = api.get_user_photos("<username>", 1, 10, false, "days", 30, "latest");A function to get a list of photos liked by a user.
GET /users/:username/likes
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| username | string | The username of the particular user | no | |
| page | int | Page number to retrieve | yes | 1 |
| per_page | int | Number of items per page | yes | 10 |
| order_by | string | How to sort the photos.(Valid values: latest, oldest, popular) |
yes | latest |
auto liked = api.get_user_liked_photos("<username>", 1, 10, "latest");A function to get a list of collections created by the user.
GET /users/:username/collections
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| username | string | The username of the particular user | no | |
| page | int | Page number to retrieve | yes | 1 |
| per_page | int | Number of items per page | yes | 10 |
auto collections = api.get_user_collections("<username>", 1, 10);A function to get a user's account statistics.
GET /users/:username/statistics
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| username | string | The username of the particular user | no | |
| resolution | string | The frequency of the stats | yes | days |
| quantity | int | The amount of for each stat | yes | 30 |
auto stats = api.get_user_statistics("<username>", "days", 30);A function to get a single page from the list of all photos.
GET /photos
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| page | int | Page number to retrieve | yes | 1 |
| per_page | int | Number of items per page | yes | 10 |
| order_by | string | How to sort the photos.(Valid values: latest, oldest, popular) |
yes | latest |
auto photos = api.list_photos(1, 10, "latest");A function to get a single page from the list of the curated photos.
GET /photos/curated
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| page | int | Page number to retrieve | yes | 1 |
| per_page | int | Number of items per page | yes | 10 |
| order_by | string | How to sort the photos.(Valid values: latest, oldest, popular) |
yes | latest |
auto photos = api.list_curated_photos(1, 10, "latest");A function to retrieve a single photo.
GET /photos/:id
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| id | string | The photo's ID | no | |
| width | int | Image width in pixels | yes | |
| height | int | Image height in pixels | yes | |
| rect | string | 4 comma-separated integers representing x, y, width, height of the cropped rectangle | yes |
Note: Supplying the optional width or height parameters will result in the custom photo URL being added to the urls object:
auto photo = api.get_photo("<id of the photo>", 500, 500, "x, y, width, height");A function to retrieve a single random photo, given optional filters.
GET /photos/random
Note: All parameters are optional, and can be combined to narrow the pool of photos from which a random one will be chosen.
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| collections | string | The public collection ID('s) to filter selection. If multiple, comma-separated | yes | |
| featured | bool | Limit selection to featured photos | yes | false |
| username | string | Limit selection to a single user | yes | |
| query | string | Limit selection to photos matching a search term | yes | |
| width | int | The Image width in pixels | yes | |
| height | int | The Image height in pixels | yes | |
| orientation | string | Filter search results by photo orientation. (Valid values are landscape, portrait, and squarish) |
yes | landscape |
| count | int | The number of photos to return. (max: 30) |
yes | 1 |
Note: You can't use the collections and query parameters in the same request. When supplying a count parameter - and only then - the response will be an array of photos, even if the value of count is 1.
auto photo = api.get_random_photo("", true, "", "nature", 800, 600, "landscape", 1);A function to retrieve total number of downloads, views and likes of a single photo, as well as the historical breakdown of these stats in a specific timeframe (default is 30 days).
GET /photos/:id/statistics
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| id | string | The photo's ID | no | |
| resolution | string | The frequency of the stats | yes | days |
| quantity | int | The amount of for each stat | yes | 30 |
Note: Currently, the only resolution param supported is "days". The quantity param can be any number between 1 and 30.
auto stats = api.get_photo_statistics("<photo-id>", "days", 10);A function to retrieve a single photo's download link. Preferably hit this endpoint if a photo is downloaded in your application for use (example: to be displayed on a blog article, to be shared on social media, to be remixed, etc).
GET /photos/:id/download
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| id | string | The photo's ID | no |
Note: This is different than the concept of a view, which is tracked automatically when you hotlink an image.
auto link = api.get_photo_link("<photo-id>");A function to update a photo on behalf of the logged-in user. This requires the write_photos scope and bearer_token.
PUT /photos/:id
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| id | string | The photo's ID | no | |
| location | json | The location object holding location data | yes | |
| exif | json | The exif object holding exif data | yes |
Note: Exchangeable image file format (officially Exif, according to JEIDA/JEITA/CIPA specifications) is a standard that specifies the formats for images, sound, and ancillary tags used by digital cameras (including smartphones), scanners and other systems handling image and sound files recorded by digital cameras. Readmore
| object[key] | Description |
|---|---|
| location[latitude] | The photo location's latitude (Optional) |
| location[longitude] | The photo location's longitude (Optional) |
| location[name] | The photo location's name (Optional) |
| location[city] | The photo location's city (Optional) |
| location[country] | The photo location's country (Optional) |
| location[confidential] | The photo location's confidentiality (Optional) |
| exif[make] | Camera's brand (Optional) |
| exif[model] | Camera's model (Optional) |
| exif[exposure_time] | Camera's exposure time (Optional) |
| exif[aperture_value] | Camera's aperture value (Optional) |
| exif[focal_length] | Camera's focal length (Optional) |
| exif[iso_speed_ratings] | Camera's iso (Optional) |
json location = {{"country", "INDIA"}};
json exif = {{"make", "Redmi Note 3"}};
api.update_photo("<photo-id>", location, exif);A function to like a photo on behalf of the logged-in user. This requires the write_likes scope.
POST /photos/:id/like
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| id | string | The photo's ID | no |
Note: This action is idempotent; sending the POST request to a single photo multiple times has no additional effect.
api.like_photo("<photo-id>");A function to remove a user's like of a photo.
DELETE /photos/:id/like
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| id | string | The photo's ID | no |
Note: This action is idempotent; sending the DELETE request to a single photo multiple times has no additional effect.
api.unlike_photo("<photo-id>");A function to get a single page of photo results for a particular query.
GET /search/photos
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| query | string | The search query | no | |
| page | int | Page number to retrieve | yes | 1 |
| per_page | int | Number of items per page | yes | 10 |
| collections | string | Collection ID('s) to narrow search. If multiple, comma-separated. | yes | |
| orientation | string | Filter search results by photo orientation. (Valid values are landscape, portrait, and squarish.) |
yes | landscape |
auto results = api.search("cars", 1, 10, "", "landscape");A function to get a single page of collection results for a query.
GET /search/collections
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| query | string | The search query | no | |
| page | int | Page number to retrieve | yes | 1 |
| per_page | int | Number of items per page | yes | 10 |
auto results = api.search_collections("cars", 1, 10);A function to get a single page of user results for a query.
GET /search/users
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| query | string | The search query | no | |
| page | int | Page number to retrieve | yes | 1 |
| per_page | int | Number of items per page | yes | 10 |
auto results = api.search_users("<search-keyword>", 1, 10);A function to get the current User's profile. To access a user's private data, the user is required to authorize the read_user scope. Without it, this request will return a 403 Forbidden response.
GET /me
Note: No Parameters are required.
Note: Without a Bearer token (i.e. using a
Client-ID token) this request will return a401 Unauthorizedresponse.
auto profile = api.get_current_user_profile();A function to update the current User's profile.
PUT /me
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| username | string | The username of the current user | yes | |
| first_name | string | The first name of the current user | yes | |
| last_name | string | The last name of the current user | yes | |
| string | The email id of the current user | yes | ||
| url | string | The Portfolio/personal URL of the current user | yes | |
| location | string | The location of the current user | yes | |
| bio | string | The About/bio of the current user | yes | |
| instagram_username | string | The Instagram username of the current user | yes |
Note: This action requires the
write_user scope. Without it, it will return a403 Forbidden response.
api.update_current_user_profile("username", "first", "last", "email", "url", "location", "bio", "instagram");A function to get a list of counts for all of Unsplash.
GET /stats/total
auto totals = api.get_stats_totals();200 OK{
"total_stats": {
"photos": 10000,
"downloads": 2000,
"views": 5000,
"likes": 800,
"photographers": 100,
"pixels": 200000,
"downloads_per_second": 10,
"views_per_second": 20,
"developers": 20,
"applications": 50,
"requests": 8000
}
}A function to get the overall Unsplash stats for the past 30 days.
GET /stats/month
auto month = api.get_stats_month();200 OK{
"month_stats": {
"downloads": 20,
"views": 200,
"likes": 60,
"new_photos": 10,
"new_photographers": 5,
"new_pixels": 2000,
"new_developers": 8,
"new_applications": 5,
"new_requests": 100
}
}Collections have the following link relations:
| rel | Description |
|---|---|
self |
API location of this collection |
html |
HTML location of this collection |
photos |
API location of this collection's photos |
related |
API location of this collection's related collections (Non-curated collections only) |
download |
Download location of this collection's zip file (Curated collections only) |
A function to get a single page from the list of all collections.
GET /collections
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| page | int | Page number to retrieve | yes | 1 |
| per_page | int | Number of items per page | yes | 10 |
auto collections = api.list_collections();A function to get a single page from the list of featured collections.
GET /collections/featured
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| page | int | Page number to retrieve | yes | 1 |
| per_page | int | Number of items per page | yes | 10 |
auto featured = api.list_featured_collections();A function to get a single page from the list of curated collections.
GET /collections/curated
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| page | int | Page number to retrieve | yes | 1 |
| per_page | int | Number of items per page | yes | 10 |
auto curated = api.list_curated_collections();A function to retrieve a single collection. To view a user's private collections, the read_collections scope is required.
GET /collections/:id
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| id | string | The Collection ID | no |
auto collection = api.get_collection("<collection-id>");A function to retrieve a single curated collection. To view a user's private collections, the read_collections scope is required.
GET /collections/curated/:id
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| id | string | The Collection ID | no |
auto collection = api.get_curated_collection("<curated-collection-id>");A function to retrieve a collection's photos.
GET /collections/:id/photos
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| id | string | The Collection ID | no | |
| page | int | Page number to retrieve | yes | 1 |
| per_page | int | Number of items per page | yes | 10 |
auto photos = api.get_collection_photos("<collection-id>", 1, 10);A function to retrieve a curated collection's photos.
GET /collections/curated/:id/photos
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| id | string | The Collection ID | no | |
| page | int | Page number to retrieve | yes | 1 |
| per_page | int | Number of items per page | yes | 10 |
auto photos = api.get_curated_collection_photos("<curated-collection-id>", 1, 10);A function to retrieve a list of collections related to this one.
GET /collections/:id/related
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| id | string | The Collection ID | no |
auto related = api.list_related_collections("<collection-id>");A function to create a new collection. This requires the write_collections scope.
POST /collections
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| title | string | The title of the collection | no | |
| description | string | The collection's description | yes | |
| private | bool | Whether to make this collection private | yes | false |
auto collection = api.create_collection("<collection-name>", "<description>", false);A function to update an existing collection belonging to the logged-in user. This requires the write_collections scope.
PUT /collections/:id
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| id | string | The collection id | no | |
| title | string | The title of the collection | yes | |
| description | string | The collection's description | yes | |
| private | bool | Whether to make this collection private | yes | false |
auto collection = api.update_collection("<collection-id>", "<collection-name>", "<description>", false);A function to delete a collection belonging to the logged-in user. This requires the write_collections scope.
DELETE /collections/:id
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| id | string | The Collection ID | no |
api.delete_collection("<collection-id>");A function to add a photo to one of the logged-in user's collections. Requires the write_collections scope.
POST /collections/:collection_id/add
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| collection_id | string | The Collection ID | no | |
| photo_id | string | The Photo ID | no |
Note: If the photo is already in the collection, this action has no effect.
api.add_photo_to_collection("<collection-id>", "<photo-id>");A function to remove a photo from one of the logged-in user's collections. Requires the write_collections scope.
DELETE /collections/:collection_id/remove
| Parameter | Type | Description | Optional | Default |
|---|---|---|---|---|
| collection_id | string | The Collection ID | no | |
| photo_id | string | The Photo ID | no |
api.remove_photo_from_collection("<collection-id>", "<photo-id>");This project uses CMake for building and GoogleTest for testing. In CI, run:
mkdir build && cd build
cmake -DWRAPSLASH_BUILD_TESTS=ON ..
cmake --build .
ctestA minimal GitHub Actions workflow can use the same commands in a standard C++ environment.
WrapSplash++ uses GoogleTest as the testing framework. Test files are available in the tests/ folder.
The MIT License
Copyright (c) 2024-2026 Sandeep Vattapparambil, http://www.sandeepv.in
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, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Thanks, and Kudos to team Unsplash for creating a wonderful platform for sharing beautiful high quality free images and photos.
Made with ❤️ by Sandeep Vattapparambil.
