Integrate Shutterstock's REST API into your Bolt.new app to search and license stock images, videos, and music. Use OAuth 2.0 or Basic Auth with your API credentials via a Next.js API route. Display watermarked preview thumbnails freely; licensing requires a paid Shutterstock subscription. The HTTP-based API works in Bolt's WebContainer for search and preview — licensing calls should go through server-side routes.
Build a Shutterstock Stock Image Search and Licensing App in Bolt.new
Shutterstock's API gives developers access to one of the largest stock media libraries on the internet — 450+ million images, 25 million videos, and millions of music tracks. For content creators, marketing agencies, and media production tools built in Bolt.new, embedding Shutterstock search and licensing directly into a workflow eliminates the need to switch to the Shutterstock website. Users can search, preview, and license assets without leaving your app.
The API splits into two tiers. The free tier (available with any registered developer app) allows unlimited search queries and access to watermarked preview thumbnails — these are the 1000px watermarked images visible on the Shutterstock website. No licensing or payment is involved at this level. The paid tier requires a Shutterstock API subscription (separate from a regular Shutterstock subscription) and unlocks licensing, downloading original files, and accessing the full-resolution unwatermarked images. For most app development and prototyping, the free preview tier is sufficient.
The Shutterstock API supports both OAuth 2.0 (for user-level authorization, allowing users to license from their own Shutterstock accounts) and HTTP Basic authentication (for app-level access using your developer credentials). For an internal tool or single-user app, Basic Auth is simpler. For a multi-user platform where each user licenses from their own account, OAuth 2.0 is the right choice. Either way, all credentials must stay server-side — proxy calls through Next.js API routes.
Integration method
Shutterstock API calls are proxied through Next.js API routes in Bolt to handle CORS restrictions and keep API credentials server-side. Search and thumbnail preview endpoints work in development. Licensing and download endpoints require a paid Shutterstock API plan and should run server-side. The API is HTTP/JSON-based and works in Bolt's WebContainer for outbound calls.
Prerequisites
- A Shutterstock developer account at developers.shutterstock.com
- An API application registered with Client ID and Client Secret (free)
- A paid Shutterstock API subscription if you need to license and download full-resolution images
- A Bolt.new project using Next.js (for server-side API routes to handle CORS and credentials)
- Basic understanding of OAuth 2.0 or HTTP Basic authentication
Step-by-step guide
Register a Shutterstock Developer App
Register a Shutterstock Developer App
Go to developers.shutterstock.com and log in with your Shutterstock account (or create a free account). Navigate to 'My Apps' and click 'Create new app'. Give your app a name, description, and website URL. For development, the callback URL can be your Netlify URL — you'll update it after deploying. After creation, Shutterstock shows your Client ID and Client Secret. Copy both. For Basic Auth (simplest approach): your authorization header is `Basic {base64(clientId:clientSecret)}`. For OAuth 2.0: use the Client Credentials flow for server-to-server access, or Authorization Code flow for user-level access. Test that your credentials work by calling the Shutterstock search endpoint with Basic Auth — a valid search for 'sunset' should return 20 results. Shutterstock's developer tier (free) allows up to 250 searches per month. For production apps with high search volume, you'll need a higher-tier plan. The preview thumbnails returned in search results are watermarked and freely displayable — you're allowed to show them in your app for the purpose of users choosing which image to license.
Create a .env.local file and a Shutterstock API utility at lib/shutterstock.ts. The utility should export a shutterstockFetch helper that adds Basic Auth headers using SHUTTERSTOCK_CLIENT_ID and SHUTTERSTOCK_CLIENT_SECRET environment variables (base64 encoded as clientId:clientSecret). Create a search function searchImages(query, page, perPage) that calls GET /v2/images/search with the query, returns results with id, description, preview URL (assets.preview.url), and contributor name.
Paste this in Bolt.new chat
1// lib/shutterstock.ts2const SS_BASE = 'https://api.shutterstock.com/v2';34function getAuthHeader(): string {5 const clientId = process.env.SHUTTERSTOCK_CLIENT_ID!;6 const clientSecret = process.env.SHUTTERSTOCK_CLIENT_SECRET!;7 return `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`;8}910export interface SSImage {11 id: string;12 description: string;13 previewUrl: string;14 previewWidth: number;15 previewHeight: number;16 contributor: string;17 categories: string[];18}1920export interface SSSearchResult {21 data: SSImage[];22 total_count: number;23 page: number;24 per_page: number;25}2627export async function searchImages(28 query: string,29 page = 1,30 perPage = 20,31 orientation?: 'horizontal' | 'vertical' | 'square'32): Promise<SSSearchResult> {33 const url = new URL(`${SS_BASE}/images/search`);34 url.searchParams.set('query', query);35 url.searchParams.set('page', String(page));36 url.searchParams.set('per_page', String(perPage));37 url.searchParams.set('view', 'full');38 if (orientation) url.searchParams.set('orientation', orientation);3940 const res = await fetch(url.toString(), {41 headers: { Authorization: getAuthHeader() },42 });4344 if (!res.ok) throw new Error(`Shutterstock search failed: ${res.status}`);4546 const json = await res.json();47 return {48 data: json.data.map((item: Record<string, unknown>) => ({49 id: item.id,50 description: item.description,51 previewUrl: (item.assets as Record<string, { url: string; width: number; height: number }>)?.preview?.url ?? '',52 previewWidth: (item.assets as Record<string, { url: string; width: number; height: number }>)?.preview?.width ?? 0,53 previewHeight: (item.assets as Record<string, { url: string; width: number; height: number }>)?.preview?.height ?? 0,54 contributor: (item.contributor as { id: string })?.id ?? '',55 categories: (item.categories as Array<{ name: string }>)?.map((c) => c.name) ?? [],56 })),57 total_count: json.total_count,58 page: json.page,59 per_page: json.per_page,60 };61}Pro tip: Shutterstock's free developer tier allows 250 searches per month. For prototyping this is sufficient. Check your usage in the Shutterstock developer portal to avoid hitting limits during active development.
Expected result: The Shutterstock utility is ready to make authenticated API calls. searchImages('sunset') returns a structured list of image results.
Create the Search API Route and Gallery Component
Create the Search API Route and Gallery Component
Create a Next.js API route that wraps the search utility and serves results to your frontend. Because Shutterstock does not whitelist browser origins, direct client-side fetch calls to the Shutterstock API fail with CORS errors — the API route proxy is essential, not optional. The route should accept `q` (search query), `page`, and `orientation` query parameters and return the normalized image list. On the frontend, build a gallery component that calls your `/api/shutterstock/search` route, renders a responsive image grid using CSS Grid or a masonry library, shows watermarked preview images (these are the standard 1000px previews you see on Shutterstock's website), and displays image metadata on hover. Implement pagination or infinite scroll by incrementing the page parameter. Add a debounce on the search input (300-500ms) to avoid excessive API calls while the user is typing. The watermarked preview URLs from the API can be used directly in `<img>` tags — Shutterstock's CDN allows hotlinking of preview images for the purpose of displaying results to end users.
Create a Next.js API route at app/api/shutterstock/search/route.ts that accepts query params: q (search query), page (default 1), per_page (default 20), and orientation. Call the searchImages function from lib/shutterstock.ts and return the results. Then build a ShutterstockGallery component that searches on input change (debounced 400ms), shows a responsive 4-column grid of watermarked preview images, displays total results count, and has Next/Previous pagination buttons.
Paste this in Bolt.new chat
1// app/api/shutterstock/search/route.ts2import { NextRequest, NextResponse } from 'next/server';3import { searchImages } from '@/lib/shutterstock';45export async function GET(request: NextRequest) {6 const params = request.nextUrl.searchParams;7 const query = params.get('q');8 const page = parseInt(params.get('page') ?? '1', 10);9 const perPage = parseInt(params.get('per_page') ?? '20', 10);10 const orientation = params.get('orientation') as 'horizontal' | 'vertical' | 'square' | undefined;1112 if (!query || query.length < 2) {13 return NextResponse.json({ data: [], total_count: 0, page: 1, per_page: perPage });14 }1516 try {17 const results = await searchImages(query, page, perPage, orientation);18 return NextResponse.json(results);19 } catch (error) {20 console.error('Shutterstock search error:', error);21 return NextResponse.json({ error: 'Search failed' }, { status: 500 });22 }23}Pro tip: Never call the Shutterstock API directly from React components — it will fail with CORS errors in all environments. All calls must go through your Next.js API routes.
Expected result: The gallery component renders a grid of watermarked Shutterstock preview images for any search term, with working pagination.
Implement Licensing for Paid Subscribers
Implement Licensing for Paid Subscribers
If your app targets users with paid Shutterstock subscriptions, you can implement the licensing flow to let them download full-resolution, unwatermarked images. Licensing requires OAuth 2.0 with the user's own Shutterstock credentials — you cannot license images on behalf of users with just your developer credentials. The Authorization Code flow involves redirecting users to Shutterstock's authorization page, receiving a code at your callback URL, exchanging it for user access tokens, and then using those tokens to call the `/v2/images/licenses` endpoint. This OAuth redirect requires a deployed URL for the callback URI — it cannot be completed in Bolt's WebContainer preview. After licensing, call `/v2/images/licenses/{license_id}/downloads` to get the download URL for the licensed image. Store the access and refresh tokens per user in your database. The licensing endpoint requires the specific image format (jpg or vector) and subscription ID.
Create a Shutterstock OAuth flow. Add app/api/shutterstock/auth/route.ts that redirects to Shutterstock's OAuth authorization URL with scope 'licenses.create'. Add app/api/shutterstock/callback/route.ts to handle the OAuth callback, exchange the code for tokens, and store them. Create app/api/shutterstock/license/route.ts that accepts an imageId and calls POST /v2/images/licenses to license the image using the user's stored access token.
Paste this in Bolt.new chat
1// app/api/shutterstock/license/route.ts2import { NextRequest, NextResponse } from 'next/server';3import { cookies } from 'next/headers';45export async function POST(request: NextRequest) {6 const cookieStore = await cookies();7 const accessToken = cookieStore.get('ss_access_token')?.value;89 if (!accessToken) {10 return NextResponse.json({ error: 'Not authenticated with Shutterstock' }, { status: 401 });11 }1213 const { imageId, format = 'jpg', subscriptionId } = await request.json();1415 const licenseRes = await fetch('https://api.shutterstock.com/v2/images/licenses', {16 method: 'POST',17 headers: {18 Authorization: `Bearer ${accessToken}`,19 'Content-Type': 'application/json',20 },21 body: JSON.stringify({22 images: [{ image_id: imageId, subscription_id: subscriptionId, format }],23 }),24 });2526 if (!licenseRes.ok) {27 const err = await licenseRes.json();28 return NextResponse.json({ error: err }, { status: licenseRes.status });29 }3031 const licenseData = await licenseRes.json();32 const licenseId = licenseData.data[0]?.license_id;3334 // Get download URL35 const downloadRes = await fetch(36 `https://api.shutterstock.com/v2/images/licenses/${licenseId}/downloads`,37 { method: 'POST', headers: { Authorization: `Bearer ${accessToken}` } }38 );3940 const downloadData = await downloadRes.json();41 return NextResponse.json({ downloadUrl: downloadData.url });42}Pro tip: Shutterstock licensing is tied to a subscription_id from the user's Shutterstock account. Fetch the user's subscriptions via GET /v2/user/subscriptions after OAuth to get the correct subscription ID.
Expected result: Authenticated users with active Shutterstock subscriptions can license images through your app and receive download URLs for full-resolution files.
Common use cases
In-App Stock Image Browser for Content Creators
Embed a Shutterstock image search directly into a blog editor, email builder, or social media tool. Users search for images without leaving the app, preview watermarked thumbnails, and click to license and insert directly into their content.
Build a Shutterstock image search component that shows a search bar and a masonry grid of watermarked preview images. When users type a search term, fetch results from /api/shutterstock/search. Show image dimensions, contributor name, and a 'License Image' button on hover. Clicking License Image should call the licensing endpoint if the user has a paid Shutterstock plan.
Copy this prompt to try it in Bolt.new
Brand Asset Library with Stock Integration
A brand asset manager that combines a company's own uploaded files with Shutterstock search results. Marketing teams can search Shutterstock, preview options, and save licensed images directly to the brand library alongside their original assets.
Create a brand asset library app that shows two tabs: 'Our Assets' (files from Supabase Storage) and 'Stock Images' (Shutterstock search). In the Stock Images tab, add a search input and a grid of Shutterstock previews. Users can click a stock image and 'Add to Library' which licenses it via the Shutterstock API and saves the licensed URL to Supabase.
Copy this prompt to try it in Bolt.new
Video Stock Footage Browser
Search Shutterstock's video library by keyword, duration range, and resolution. Display video previews with duration and category filters. Useful for video production tools, presentation builders, or content creation platforms.
Build a Shutterstock video search tool. Users can search for stock video footage by keyword and filter by duration (under 30s, 30-60s, over 60s) and resolution (4K, HD). Display results in a grid with video thumbnails, duration badge, and contributor name. Use the Shutterstock API /v2/videos/search endpoint. Show a preview on hover.
Copy this prompt to try it in Bolt.new
Troubleshooting
CORS error when calling Shutterstock API from the browser
Cause: Shutterstock's API does not allow browser origins. All API calls must go through a server-side proxy.
Solution: All Shutterstock API calls must be proxied through Next.js API routes (app/api/shutterstock/). Never call api.shutterstock.com directly from React components or client-side code.
1// WRONG — direct browser call2const data = await fetch('https://api.shutterstock.com/v2/images/search?query=sunset');34// CORRECT — proxy through your API route5const data = await fetch('/api/shutterstock/search?q=sunset');Search returns 401 Unauthorized with Basic Auth
Cause: The Basic Auth header is malformed — Client ID and Client Secret must be base64 encoded together as 'clientId:clientSecret', not separately.
Solution: Verify the auth header construction in lib/shutterstock.ts. The colon-separated string must be encoded as one unit: Buffer.from(`${clientId}:${clientSecret}`).toString('base64').
1// CORRECT encoding2const auth = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');3const header = `Basic ${auth}`;Preview images show as broken in the gallery
Cause: The preview URL from the API response may be nested under different asset size keys depending on the API response fields requested.
Solution: Check the full API response structure in your browser's DevTools Network tab or log the raw response. Use the 'preview' or 'small_thumb' asset URLs, which are consistently available for all images. Add view=full to your search query to get all asset URLs in the response.
1// Add ?view=full to include all asset URLs in the response2url.searchParams.set('view', 'full');OAuth callback fails during development in Bolt's preview
Cause: Bolt's WebContainer uses a dynamic preview URL that cannot be registered as a stable OAuth redirect URI in Shutterstock's developer portal.
Solution: Deploy to Netlify or Bolt Cloud first, then register your deployed URL as the OAuth callback in the Shutterstock developer portal. Test the OAuth licensing flow on the deployed site, not in the preview. For development, test search and preview functionality using Basic Auth, which doesn't require OAuth.
Best practices
- Never call the Shutterstock API directly from client-side code — all requests must be proxied through Next.js API routes to handle CORS and protect credentials
- Display watermarked preview images only for selection purposes — using previews as final deliverables in published content violates Shutterstock's Terms of Service
- Clearly distinguish between 'preview' (free, watermarked) and 'licensed' (paid, unwatermarked) states in your UI
- Cache search results for repeated queries to avoid hitting monthly API call limits on the free developer tier
- For multi-user apps requiring licensing, implement OAuth 2.0 so each user licenses from their own Shutterstock subscription rather than sharing a single account's licenses
- Test OAuth callback flows on a deployed URL — the WebContainer preview URL is dynamic and cannot be pre-registered as an OAuth redirect URI
- Include attribution or source information (Shutterstock contributor) in your UI as good practice, even though it may not be contractually required for all license types
Alternatives
Pixabay offers free Creative Commons stock images with no licensing fees — simpler to integrate and ideal for projects that don't need commercial premium imagery.
Getty Images provides premium editorial and creative stock content at higher quality and exclusivity than Shutterstock, targeting enterprise media buyers.
Canva's API provides access to its design editor and asset library, useful when you need a full design workflow rather than just image search and licensing.
Frequently asked questions
Can I display Shutterstock images in my app for free?
You can display watermarked preview thumbnails (the same images shown on Shutterstock's website) for free as part of a search/selection interface. Using these watermarked previews as final deliverables or in published content is not allowed. Full-resolution, unwatermarked images require licensing through a paid Shutterstock subscription.
Does Shutterstock API work in Bolt's WebContainer preview?
Outbound HTTPS calls to Shutterstock's API work in Bolt's WebContainer, but they must go through Next.js API routes rather than direct client-side fetch calls (due to CORS). The full OAuth licensing flow cannot be tested in the preview because the callback URL is dynamic — test licensing on your deployed URL.
How do I connect Bolt.new to Shutterstock for image search?
Register a developer app at developers.shutterstock.com to get a Client ID and Client Secret. Add these to your .env.local file. Create a Next.js API route that uses HTTP Basic Auth (base64(clientId:clientSecret)) to proxy search calls to Shutterstock's /v2/images/search endpoint. Display the returned preview thumbnails in your gallery component.
What's the difference between Shutterstock API Basic Auth and OAuth 2.0?
Basic Auth uses your developer app credentials and is suitable for app-level access — searching and displaying previews. OAuth 2.0 is needed when users want to license images from their own Shutterstock subscriptions. For a single-user tool or preview-only app, Basic Auth is simpler. For a platform serving multiple users who each have their own Shutterstock accounts, implement OAuth 2.0.
How do I deploy a Shutterstock image app from Bolt.new to Netlify?
Connect Netlify in Bolt Settings → Applications and click Publish. Add SHUTTERSTOCK_CLIENT_ID and SHUTTERSTOCK_CLIENT_SECRET to Netlify's environment variables under Site Configuration → Environment Variables. If using OAuth, register your Netlify URL as the callback URI in the Shutterstock developer portal. Redeploy after adding environment variables.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation