Pixabay
Category: Stock Images
Integration type: Platform-level API key
External API: Pixabay API v2
Purpose
Pixabay provides free-to-use stock photos, illustrations, and vectors licensed under the Pixabay License (free for commercial use, no attribution required). It is used by the Blog Writer, Social Post Writer, and GBP Post Writer agents to find and attach relevant images to generated content.
Pixabay is the platform default image source — no tenant configuration required. All search costs are absorbed by the platform (free API for low-to-medium volume).
See also Unsplash for higher-quality photography when required.
Config Structure
Platform config (env vars)
PIXABAY_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # From pixabay.com/api/docs/
PIXABAY_API_BASE_URL=https://pixabay.com/api/Integration Pattern
Tool layer (packages/tools/src/pixabay.ts)
import axios from 'axios';
class PixabayTool {
constructor(
private apiKey: string,
private baseUrl: string = 'https://pixabay.com/api/',
) {}
async searchImages(options: {
query: string;
imageType?: 'photo' | 'illustration' | 'vector'; // Default: 'photo'
orientation?: 'horizontal' | 'vertical' | 'all'; // Default: 'horizontal'
category?: string; // 'backgrounds' | 'fashion' | 'nature' | 'science' | 'technology' | etc.
minWidth?: number; // Minimum image width in pixels
minHeight?: number;
safeSearch?: boolean; // Default: true
order?: 'popular' | 'latest'; // Default: 'popular'
page?: number; // Default: 1
perPage?: number; // 3–200, Default: 20
}): Promise<PixabaySearchResult> {
const response = await axios.get(this.baseUrl, {
params: {
key: this.apiKey,
q: options.query,
image_type: options.imageType ?? 'photo',
orientation: options.orientation ?? 'horizontal',
category: options.category,
min_width: options.minWidth,
min_height: options.minHeight,
safesearch: options.safeSearch !== false ? 'true' : 'false',
order: options.order ?? 'popular',
page: options.page ?? 1,
per_page: options.perPage ?? 20,
},
});
return {
totalHits: response.data.totalHits,
total: response.data.total,
images: response.data.hits.map((hit: any) => ({
id: hit.id,
pageUrl: hit.pageURL,
type: hit.type,
tags: hit.tags.split(', '),
previewUrl: hit.previewURL, // 150px thumbnail
webUrl: hit.webformatURL, // ~640px web-ready
largeUrl: hit.largeImageURL, // ~1280px
fullHdUrl: hit.fullHDURL, // Full HD (requires API key tier)
width: hit.imageWidth,
height: hit.imageHeight,
likes: hit.likes,
views: hit.views,
downloads: hit.downloads,
photographer: hit.user,
photographerUrl: `https://pixabay.com/users/${hit.user}-${hit.user_id}/`,
})),
};
}
async searchVideos(options: {
query: string;
videoType?: 'film' | 'animation' | 'all';
order?: 'popular' | 'latest';
page?: number;
perPage?: number;
}): Promise<PixabayVideoResult> {
const response = await axios.get(`${this.baseUrl}videos/`, {
params: {
key: this.apiKey,
q: options.query,
video_type: options.videoType ?? 'all',
order: options.order ?? 'popular',
page: options.page ?? 1,
per_page: options.perPage ?? 20,
},
});
return {
totalHits: response.data.totalHits,
videos: response.data.hits.map((hit: any) => ({
id: hit.id,
pageUrl: hit.pageURL,
tags: hit.tags.split(', '),
duration: hit.duration,
videos: {
tiny: hit.videos.tiny?.url,
small: hit.videos.small?.url,
medium: hit.videos.medium?.url,
large: hit.videos.large?.url,
},
photographer: hit.user,
})),
};
}
async verify(): Promise<void> {
const result = await this.searchImages({ query: 'test', perPage: 3 });
if (result.total === undefined) {
throw new Error('Pixabay API verification failed');
}
}
}Agent image selection
When the Blog Writer or Social Post Writer produces content, the agent’s tool integration can call pixabay_search to find relevant images:
// Tool registration in agent execution engine
{
name: 'pixabay_search',
description: 'Search for free stock images relevant to the content being created',
parameters: {
query: { type: 'string' },
imageType: { type: 'string', enum: ['photo', 'illustration', 'vector'] },
orientation: { type: 'string', enum: ['horizontal', 'vertical'] },
},
}The agent returns image IDs and URLs; the platform downloads the selected image to S3, then uses the S3 URL for WordPress/Instagram/etc. publishing.
Image Download and Storage
Images are not stored long-term — they are downloaded on use:
// Before publishing to WordPress/Instagram
async function fetchAndStoreImage(
pixabayImage: PixabayImage,
tenantId: string,
activityId: string,
): Promise<string> {
const imageBuffer = await axios.get(pixabayImage.largeUrl, { responseType: 'arraybuffer' });
const s3Key = `media/${tenantId}/pixabay/${activityId}/${pixabayImage.id}.jpg`;
const { url } = await s3.upload({
key: s3Key,
body: Buffer.from(imageBuffer.data),
contentType: 'image/jpeg',
});
return url;
}Pixabay License
Images from Pixabay are licensed under the Pixabay License:
- Free for commercial and non-commercial use
- No attribution required (but encouraged)
- Cannot sell the images as-is or redistribute on stock image platforms
The platform should not claim copyright over Pixabay images — attribution can optionally be added to blog posts as Photo by {photographer} via Pixabay.
Test Cases
Unit tests (packages/tools/src/pixabay.test.ts)
| Test | Approach |
|---|---|
searchImages() builds correct params | Mock axios.get; assert key, q, image_type, safesearch |
searchImages() defaults to safesearch: true | Assert safesearch === 'true' when option not set |
searchImages() maps tags string to array | Mock tags: 'dog, outdoor, park'; assert ['dog', 'outdoor', 'park'] |
searchImages() exposes all image URL variants | Assert previewUrl, webUrl, largeUrl mapped |
searchVideos() uses /videos/ endpoint | Assert URL contains /api/videos/ |
verify() throws when response malformed | Mock {}; assert throws |
| Throws on 429 (rate limit) | Mock 429; assert propagated |
Related
- Unsplash Provider — premium photography alternative
- AWS S3 Provider — image storage before publishing
- Blog Writer Agent — primary consumer
- Social Post Writer Agent — social image selection