To integrate the Shutterstock API with V0 by Vercel, generate a media search UI with V0, create a Next.js API route that proxies Shutterstock image and video search using your API credentials, and store your client ID and client secret in Vercel environment variables. Your app can search Shutterstock's 400M+ asset library, display previews, and handle licensing — without exposing credentials to the browser.
Build a Shutterstock Media Browser and Search Interface with V0 and Next.js
Shutterstock's API opens access to one of the world's largest commercial stock media libraries — over 400 million photos, vectors, illustrations, editorial images, videos, and music tracks. For developers building V0-powered apps that need professional media selection, content creation tools, or media management workflows, the Shutterstock API provides comprehensive search, filtering, collection management, and licensing capabilities through a well-documented REST interface.
The API has two access tiers: a public search tier using client credentials (OAuth 2.0 client credentials grant) that allows searching and displaying watermarked previews, and a user-specific tier requiring per-user OAuth tokens that allows purchasing licenses and downloading full-resolution assets billed to the user's Shutterstock subscription. For most V0 integration use cases — building a content library browser, an asset search tool, or a media picker for a CMS — the client credentials flow is sufficient for search and preview display. Actual licensing requires a user's Shutterstock account and OAuth consent.
The Shutterstock API base URL is https://api.shutterstock.com/v2/ and supports comprehensive query parameters for image search: keywords, image_type (photo/illustration/vector), orientation, color, safe (safe search), and sort (relevance/newest/popular). Search results include asset IDs, display names, contributor information, keywords, and preview image URLs. The watermarked preview images are safe to display in your app without licensing — they carry Shutterstock's watermark overlay and are for preview purposes only.
Integration method
The Shutterstock API integrates with V0-generated Next.js apps through server-side API routes that authenticate using a client credentials OAuth 2.0 flow. Your Shutterstock client ID and client secret are stored as server-only Vercel environment variables. API routes proxy search and licensing requests to Shutterstock's API, returning only the safe-to-display data (preview URLs, metadata, asset IDs) to the browser. Download URLs for licensed assets are generated server-side, and actual licensing operations require user-specific OAuth tokens for billing against a user account.
Prerequisites
- A Shutterstock developer account — register at developers.shutterstock.com (free account creation, API access is free for search and preview)
- A Shutterstock application created in the developer portal — go to developers.shutterstock.com → My Apps → Create App to get a client ID and client secret
- Your Shutterstock client ID and client secret from the developer portal — the client ID is safe to use publicly for some flows but treat the client secret as a server-only secret
- A V0 account at v0.dev and a Vercel account for deployment
- Note: Actual image licensing and downloads require a paid Shutterstock subscription and user-specific OAuth authorization — this guide covers search and preview display using client credentials
Step-by-step guide
Generate the Media Search UI with V0
Generate the Media Search UI with V0
Open V0 at v0.dev and describe the media browser or search interface you want to build. Shutterstock's API returns rich image data — preview URLs, thumbnail URLs, asset dimensions, keywords, contributor names, and category information. V0 generates excellent image gallery components when you describe the specific layout: masonry grids, uniform grid layouts, list views with metadata, or card-based layouts all work well. For stock image browsers, describe the exact search interaction flow: what happens when the user types in the search bar (debounced search or submit-on-enter), how images are displayed in results (thumbnail size, whether contributor name shows, whether keywords are visible), what hovering or clicking an image does (selection state, detail modal, or inline preview expansion), and how the selected/licensed state is communicated visually. Specify that the component should call GET /api/shutterstock/search with a query parameter when the user searches. The API will return an array of image objects with fields like id, description, preview.url, preview.width, preview.height, contributor.id, and keywords. Ask V0 to design the component to receive this array as props or to fetch it directly. The most common issue with image galleries is handling different aspect ratios — a masonry layout handles this best, or you can specify that images should be displayed in a uniform grid with cover-fit cropping.
Create a stock photo search interface. At the top, a search bar with a magnifying glass icon and placeholder 'Search photos, vectors, illustrations...'. Below, filter chips: All Types / Photos / Vectors / Illustrations and Horizontal / Vertical / Square. The results area shows a 3-column grid on desktop with image thumbnails (16:9 aspect ratio, object-cover), contributor name below each image, and a bookmark icon in the top-right corner of each card. On hover, each card shows a subtle overlay with the image description. Clicking a card opens a modal with: large watermarked preview (centered), description, keywords as chips, contributor name, resolution, and a blue 'License This Image' button. The grid fetches from GET /api/shutterstock/search?query=Q&type=all on search submit. Show a 9-card skeleton grid while loading.
Paste this in V0 chat
Pro tip: Ask V0 to use a masonry grid layout rather than a uniform grid for displaying photos — masonry layout preserves the natural aspect ratio of each image and looks more professional for stock photo browsing.
Expected result: A stock image search interface renders in V0's preview with a search bar, filter chips, and an image grid displaying placeholder images. The component is wired to call /api/shutterstock/search on search submission.
Create the Shutterstock Search API Route
Create the Shutterstock Search API Route
Create a Next.js API route that authenticates with Shutterstock using the client credentials OAuth flow and proxies search requests. The Shutterstock OAuth token endpoint is https://api.shutterstock.com/v2/oauth/access_token and accepts a POST with grant_type: 'client_credentials', client_id, client_secret, and scope: 'licenses.create,purchases.view,licenses.view,user.view'. The access token returned is valid for approximately 30 days (check the expires_in field in the response) and should be cached in a module-level variable to avoid authenticating on every search request. All Shutterstock API requests use the access token as a Bearer token in the Authorization header. The image search endpoint is GET /v2/images/search with query parameters: query (search keywords), image_type (photo/illustration/vector), orientation (horizontal/vertical/square), safe (true/false for safe search), sort (relevance/newest/popular/random), per_page (number of results, max 500), and page (pagination). The response contains a data array of image objects with preview URLs, asset IDs, descriptions, keywords, and contributor data. Important: the preview image URLs returned by the Shutterstock API include watermarks and are intended only for display purposes. These preview images are served from Shutterstock's CDN and will load directly in browser img tags — you do not need to proxy them through your server.
1// app/api/shutterstock/search/route.ts2import { NextRequest, NextResponse } from 'next/server';34const SHUTTERSTOCK_API = 'https://api.shutterstock.com/v2';56let cachedToken: { token: string; expiresAt: number } | null = null;78async function getAccessToken(): Promise<string> {9 if (cachedToken && Date.now() < cachedToken.expiresAt) {10 return cachedToken.token;11 }1213 const clientId = process.env.SHUTTERSTOCK_CLIENT_ID;14 const clientSecret = process.env.SHUTTERSTOCK_CLIENT_SECRET;1516 if (!clientId || !clientSecret) {17 throw new Error('Shutterstock credentials are not configured');18 }1920 const response = await fetch(`${SHUTTERSTOCK_API}/oauth/access_token`, {21 method: 'POST',22 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },23 body: new URLSearchParams({24 grant_type: 'client_credentials',25 client_id: clientId,26 client_secret: clientSecret,27 scope: 'licenses.view purchases.view user.view',28 }),29 });3031 if (!response.ok) {32 const error = await response.json();33 throw new Error(error.error_description || 'Shutterstock authentication failed');34 }3536 const data = await response.json();37 const expiresIn = (data.expires_in || 2592000) * 1000; // Default 30 days in ms38 cachedToken = { token: data.access_token, expiresAt: Date.now() + expiresIn - 60000 };39 return data.access_token;40}4142export async function GET(request: NextRequest) {43 const { searchParams } = request.nextUrl;44 const query = searchParams.get('query');45 const imageType = searchParams.get('type') || 'all';46 const orientation = searchParams.get('orientation') || '';47 const sort = searchParams.get('sort') || 'relevance';48 const page = searchParams.get('page') || '1';49 const perPage = Math.min(parseInt(searchParams.get('perPage') || '20'), 50);5051 if (!query) {52 return NextResponse.json({ error: 'query parameter is required' }, { status: 400 });53 }5455 try {56 const token = await getAccessToken();5758 const params = new URLSearchParams({59 query,60 sort,61 page,62 per_page: perPage.toString(),63 safe: 'true',64 view: 'minimal',65 });6667 if (imageType !== 'all') params.set('image_type', imageType);68 if (orientation) params.set('orientation', orientation);6970 const response = await fetch(71 `${SHUTTERSTOCK_API}/images/search?${params.toString()}`,72 { headers: { Authorization: `Bearer ${token}` } }73 );7475 if (!response.ok) {76 throw new Error(`Shutterstock search failed: ${response.statusText}`);77 }7879 const data = await response.json();8081 const images = (data.data || []).map((img: Record<string, unknown>) => ({82 id: img.id,83 description: img.description,84 contributor: (img.contributor as Record<string, unknown>)?.id,85 preview: img.assets && (img.assets as Record<string, unknown>).preview,86 thumbnail: img.assets && (img.assets as Record<string, unknown>).small_thumb,87 keywords: (img.keywords as string[] || []).slice(0, 10),88 mediaType: img.media_type,89 }));9091 return NextResponse.json({92 images,93 total: data.total_count,94 page: data.page,95 perPage: data.per_page,96 });97 } catch (error) {98 const message = error instanceof Error ? error.message : 'Search failed';99 return NextResponse.json({ error: message }, { status: 500 });100 }101}Pro tip: Cache the Shutterstock access token in a module-level variable — client credentials tokens are valid for 30 days and there is no need to re-authenticate on every search request. Re-authentication on every request significantly slows down searches.
Expected result: GET /api/shutterstock/search?query=mountain+sunset returns an array of image objects with IDs, preview URLs, descriptions, and keywords. Images display with Shutterstock watermarks in the browser as expected for preview-mode access.
Connect the Search UI to the API Route
Connect the Search UI to the API Route
Update your V0-generated search component to fetch real Shutterstock results from your API route and display them. The API route returns image objects with preview URLs in the preview field and thumbnail URLs in the thumbnail field — use the thumbnail URL for grid display (faster loading, smaller file) and the preview URL for the detail modal. Implement debounced search so the API is not called on every keystroke — wait 400-500ms after the user stops typing before triggering a search. The search component should maintain search state: the current query, selected filters (image type, orientation), current page, and whether results are loading. For pagination, add Previous/Next buttons that increment the page parameter and re-fetch. When displaying images in the grid, set the img src to the thumbnail URL (from the API response) — these are direct Shutterstock CDN URLs and load quickly. The Shutterstock CDN serves images with CORS headers, so they load cleanly in browser img tags without needing your server to proxy the image bytes. Test the integration locally by adding SHUTTERSTOCK_CLIENT_ID and SHUTTERSTOCK_CLIENT_SECRET to your .env.local file, running npm run dev, and searching for a few terms. Verify that watermarked preview images appear in the grid. If images don't load, check the browser console for CORS errors and verify the preview URL format in the API response.
Update the stock photo search to fetch real Shutterstock data. On search submit, call GET /api/shutterstock/search?query=TERM&type=SELECTED_TYPE&orientation=SELECTED_ORIENTATION. Map the response images array to the grid, using thumbnail.url for each grid card image src and preview.url for the modal preview. Display the image description as a tooltip on hover and in the modal. Show the total result count near the search bar (e.g., '12,456 results'). Implement pagination: show Previous/Next buttons below the grid and pass the page parameter to the API. Add a debounce of 500ms on the search input so API calls fire only after the user stops typing. Show empty state if no results are found for the search term.
Paste this in V0 chat
Pro tip: Use the Shutterstock thumbnail URLs directly as img src attributes — Shutterstock's CDN handles delivery and the images include Shutterstock's watermark, which is expected for preview-mode API access. There is no need to proxy image bytes through your Next.js server.
Expected result: Searching for terms like 'office meeting' or 'mountain sunset' returns real Shutterstock images with watermarks in the grid. Pagination loads the next page of results. Clicking an image opens the detail modal with the full preview and metadata.
Add Credentials to Vercel and Deploy
Add Credentials to Vercel and Deploy
Push your code to GitHub and configure Shutterstock credentials in the Vercel Dashboard. Navigate to Settings → Environment Variables and add two variables: SHUTTERSTOCK_CLIENT_ID (the client ID from your Shutterstock developer application, found at developers.shutterstock.com → My Apps — a long alphanumeric string) and SHUTTERSTOCK_CLIENT_SECRET (the client secret from the same app page — treat this as a server-only secret). Neither should have the NEXT_PUBLIC_ prefix. Set both for Production and Preview environments and save. After redeploying, test the search on your Vercel URL by searching for a common term like 'business people' — results should appear within 1-2 seconds. The first request will take slightly longer as the API route authenticates with Shutterstock and fetches the access token; subsequent requests use the cached token and are much faster. If you receive a 401 error from Shutterstock, verify the client credentials in Vercel match exactly what is shown in the Shutterstock developer portal. If you need to implement actual image licensing (allowing users to purchase and download full-resolution images through your app), you will need to implement a per-user Shutterstock OAuth flow where each user authorizes your app to license images on their behalf — this requires a Shutterstock subscription and the user-based OAuth flow, which is a more complex integration. For enterprise licensing workflows, RapidDev's team can help design the full user authorization and download architecture.
Pro tip: Shutterstock client credentials tokens last 30 days, but if you deploy to a new Vercel serverless function instance, the module-level cache is empty and a fresh token request is needed. This is normal — the authentication call adds only ~200ms to the first request from each new function instance.
Expected result: The deployed Vercel app searches Shutterstock and returns watermarked preview images. Search performance is fast after the initial token fetch. Vercel Function Logs show successful Shutterstock API authentication.
Common use cases
Stock Image Search and Browser
A searchable stock image browser embedded in a CMS, marketing tool, or content creation app. Users search by keyword, filter by orientation and image type, and select images to use in their content — with the app handling licensing through Shutterstock's standard licensing endpoint.
Create a stock image search interface with a prominent search bar at the top, filter chips below for orientation (Horizontal/Vertical/Square) and type (Photo/Illustration/Vector), and a masonry grid layout showing 20 image results. Each image card shows the watermarked preview, contributor name, and a heart button for saving to collection. Clicking an image opens a detail modal showing larger preview, keywords, image ID, and a 'License Image' button. The search calls GET /api/shutterstock/search?query=TERM on submit. Show a skeleton loading state while results load. Use a clean modern design with white background and dark typography.
Copy this prompt to try it in V0
Creative Asset Management Dashboard
An internal media asset management tool where team members can search Shutterstock for images, save them to project collections, and track licensing status. The dashboard shows licensed assets by project, with each asset's download link and license expiry date.
Build a media asset management dashboard with a left sidebar showing 'My Projects' with project names and image counts. The main area shows a two-tab interface: 'Search' tab with a Shutterstock search interface (search bar, results grid, save-to-project button per image) and 'Licensed Assets' tab showing previously licensed images grouped by project with thumbnail, license date, and download button. The search tab fetches from /api/shutterstock/search. The licensed assets tab fetches from /api/shutterstock/licenses. Use a professional creative tool aesthetic with dark accents and image-forward layout.
Copy this prompt to try it in V0
Editorial Image Discovery Tool
A newsroom or editorial tool that searches Shutterstock's editorial image collection — news events, sports, entertainment, and stock editorial photography — with filters for date, category, and restriction status. Results display with editorial usage restrictions noted clearly.
Design an editorial image search tool for journalists. Include a keyword search bar, category filter (Entertainment/Sports/News/Politics/Business), date range picker, and an 'Editorial Only' toggle. Display results in a tight grid with image title, date taken, contributor, and 'Editorial Use Only' badge where applicable. Include a details panel on the right that slides in when an image is selected, showing full resolution dimensions, keywords, editorial notes, and usage restrictions. The tool fetches from GET /api/shutterstock/search?category=editorial&query=TERM. Use a professional dark-themed news tool design.
Copy this prompt to try it in V0
Troubleshooting
Shutterstock authentication returns 400 or 'invalid_client' error
Cause: The SHUTTERSTOCK_CLIENT_ID or SHUTTERSTOCK_CLIENT_SECRET in Vercel does not match the credentials in the Shutterstock developer portal, or the application was deleted or its credentials were regenerated.
Solution: Navigate to developers.shutterstock.com → My Apps and verify your application exists and is active. Click on the app to view the Client ID and Client Secret — copy them exactly and update the Vercel environment variables. If the app was deleted, create a new application and update both variables. After updating credentials, redeploy the project.
Search returns results but images do not display — blank squares or broken image icons
Cause: The preview URL format in the API response may have changed, or the img src is pointing to the wrong field in the response object (description vs preview.url vs assets.preview.url — the exact path varies by API version).
Solution: Log the full API response for one search result in your API route using console.log() and check the Vercel Function Logs to see the exact response structure. Shutterstock may nest preview URLs under assets.preview.url or preview.url depending on the view parameter. Adjust your response mapping to extract the correct URL path from the actual API response structure.
API returns 403 Forbidden when attempting to search
Cause: The Shutterstock API plan associated with your developer account does not have access to the search endpoint, or the client credentials scope does not include the required permissions.
Solution: Verify in the Shutterstock developer portal that your application has the correct scopes assigned. For basic image search, the scope should include at minimum 'user.view'. The free developer tier of Shutterstock's API supports search and preview display — if you're using a new account, ensure the application is fully set up and approved in the developer portal.
Best practices
- Cache the Shutterstock OAuth access token in a module-level variable with expiry checking — tokens last 30 days and re-authenticating on every search request wastes time and counts against rate limits
- Implement debounced search (400-500ms delay) in your search component to avoid sending a request on every keystroke — this reduces unnecessary API calls and improves the user experience
- Use the thumbnail URL (small_thumb) for grid display and the preview URL for detail views — thumbnails load significantly faster and are sufficient for gallery browsing
- Display watermarked previews clearly in your UI with a visible 'Preview — License Required' label to ensure users understand they are seeing preview images, not licensed assets
- Never attempt to scrape or download full-resolution Shutterstock images without proper licensing — this violates Shutterstock's terms of service and the API agreement
- Respect Shutterstock's API rate limits (2 calls per second for standard developer accounts) by implementing client-side request throttling or a server-side queue for high-traffic apps
- For apps allowing users to license images, implement per-user OAuth so licensing costs are billed to each user's Shutterstock subscription rather than a shared application account
Alternatives
Use Getty Images if you need premium editorial and archival photography with per-image licensing — Getty's API provides access to exclusive content not available on Shutterstock, including historical and wire service editorial images.
Choose Pixabay if you need a free stock image API with no licensing requirements — Pixabay images are free to use commercially without attribution, making it ideal for apps where users need images without Shutterstock subscription costs.
Consider Vimeo if your primary need is professional video content rather than stock photography — Vimeo's API provides access to hosted video with embed capabilities rather than licensed stock footage.
Frequently asked questions
Do I need a Shutterstock subscription to use the API for image search?
No — the Shutterstock API allows searching and displaying watermarked preview images using developer credentials without a Shutterstock subscription. This is sufficient for building image browsers where users select images for licensing. Actual image licensing and downloading full-resolution assets requires a Shutterstock subscription and per-user OAuth authorization.
Are the watermarked preview images safe to display on my website?
Yes — Shutterstock provides watermarked preview images specifically for display in applications built on the API. The watermarks serve as a visual indicator that the image requires licensing before commercial use. You may display these previews in your app as part of a selection or browsing experience. Using them as final production images (cropping out the watermark, for example) violates Shutterstock's terms of service.
How do I implement Shutterstock image licensing in my V0 app?
Image licensing requires per-user OAuth authorization — each user must authorize your application to license images on their behalf using their Shutterstock subscription. Implement the OAuth authorization code flow to get a user-specific access token, then call POST /v2/images/licenses with the image ID and license type. The user is billed based on their Shutterstock subscription. This is a more complex integration than basic search — contact Shutterstock's developer support for guidance on the licensing implementation.
What is the rate limit for the Shutterstock API?
The Shutterstock developer API allows 2 requests per second for standard developer accounts. Enterprise API plans have higher rate limits. For search-focused apps, implementing debounced search (300-500ms delay after the user stops typing) keeps you well within these limits. If your app needs higher throughput, contact Shutterstock to discuss enterprise API access.
Can I search Shutterstock videos through the same API?
Yes — the Shutterstock API supports video search at GET /v2/videos/search with the same query parameters as image search. Video results include preview clip URLs (short watermarked video previews) and thumbnail images. The authentication and API structure are identical to image search — you can use the same access token and API route pattern, just changing the endpoint path from /images/search to /videos/search.
How do I filter Shutterstock search results by color?
Use the color parameter in the image search query — pass a hex color code without the # symbol (e.g., color=FF6B35). Shutterstock returns images where the specified color is dominant. You can also filter by category using the category parameter (e.g., category=finance), by number of people (people_number), by age range (people_age), and by ethnicity. Check the Shutterstock API reference at developers.shutterstock.com for the full list of available filter parameters.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation