AI-Powered Search
Discover assets using natural language and semantic understanding with Playbook's AI Search.
Overview
AI Search goes beyond keyword matching to understand the meaning and context of your queries. Instead of exact text matches, AI Search finds assets based on visual similarity, concept understanding, and semantic relevance.
AI Search vs. Regular Search
| Feature | Regular Search | AI Search |
|---|---|---|
| Method | Text/keyword matching | Semantic understanding |
| Query Style | Exact terms | Natural language |
| Results | Title/tag matches | Concept-based matches |
| Visual Understanding | No | Yes |
| Best For | Known filenames | Exploring, discovering |
Prerequisites
- Access Token: API token with search permissions
- Organization Slug: Your organization identifier
- AI Search Access: Feature must be enabled for your organization
Basic AI Search
Simple Query
curl "https://api.playbook.com/v1/my-org/ai_search?query=sunset+beach+photos" \
-H "Authorization: Bearer YOUR_TOKEN"
Response:
{
"data": [
{
"id": 301,
"token": "coastal-sunset",
"title": "Golden Hour Coast",
"media_type": "image/jpeg",
"display_url": "https://cdn.playbook.com/coastal-sunset.jpg"
},
{
"id": 302,
"token": "beach-evening",
"title": "Evening Beach Walk",
"media_type": "image/jpeg",
"display_url": "https://cdn.playbook.com/beach-evening.jpg"
}
],
"pagy": {
"parent_id": "search-token-123",
"after_cursor": "0.85@2026-08-21T12:00:00Z@1",
"has_next_page": true
}
}
Natural Language Queries
AI Search understands natural language - ask questions the way you'd ask a person:
# Find conceptually related assets
"images that would work well for a summer campaign"
# Describe what you're looking for
"product photos with white background and good lighting"
# Ask for specific moods or styles
"minimalist designs with lots of negative space"
# Find by use case
"images suitable for social media headers"
Advanced Queries
Combining Concepts
# Multiple concepts
curl "https://api.playbook.com/v1/my-org/ai_search?query=professional+headshot+bright+background+smiling" \
-H "Authorization: Bearer YOUR_TOKEN"
Filtering AI Search Results
Combine AI search with traditional filters for precision:
curl --globoff "https://api.playbook.com/v1/my-org/ai_search?query=modern+workspace&filters[media_type]=image/jpeg&filters[boards][]=office-photos" \
-H "Authorization: Bearer YOUR_TOKEN"
Available Filters. Note that boards and recursive_boards are alternatives — sending both in
one request returns 422, so only boards appears here. See Scoping to a board just below for
which one to use.
{
"filters": {
"media_type": "image/jpeg",
"boards": ["board-token-1", "board-token-2"],
"title": "contains-this-text",
"tags": ["approved", "branding"],
"tags_op": "and",
"statuses": ["Approved"],
"uploaded_by": ["asg_9f2c8a1b"],
"date_from": "2026-08-13",
"date_to": "2026-08-20",
"date_field": "uploaded"
}
}
statuses matches status and custom field values — the built-in Status and every custom field
alike — case-insensitively, and several values match any of them. Unlike the keyword
search endpoint there is no statuses_op: AI Search has no and mode for statuses.
Scoping to a board — and why boards alone may return nothing
With no board filter, AI Search covers the entire workspace — every board you can see. For a moodboard, a pitch reel or a client hand-off that usually means most of what comes back belongs to other projects, so scope the search.
boards matches an asset's own board. A board whose assets all live in sub-boards therefore
matches nothing, and the search comes back empty as though the board had nothing like your query in
it. Use recursive_boards when the token might be a parent: it matches the board and everything
nested under it.
curl --globoff "https://api.playbook.com/v1/my-org/ai_search?query=warm+interior+light&filters[recursive_boards][]=client-delivery" \
-H "Authorization: Bearer YOUR_TOKEN"
The two are mutually exclusive — sending both returns 422, because only one board clause can be
applied and picking one for you would silently answer a different question.
Subtree matching is served by the search index, which is refreshed when an asset is re-indexed, so
it can briefly lag a board being moved. When you need a guaranteed-complete list of everything
under a board rather than a relevance-ranked one, use
GET /v1/{slug}/assets?collection_token=…&nested_assets=true — it reads the database directly. See
Fetching Data.
Filtering by who uploaded, and when
uploaded_by takes workspace member tokens — look a person up with
GET /v1/{slug}/users?query= first. A plain name is accepted as a fallback and
resolved for you, but only when it matches exactly one member; anything ambiguous
fails with 422 and lists the candidates rather than guessing.
date_field chooses which date date_from/date_to apply to: original (the file's
own creation date, from EXIF where available) or uploaded (when it was added to
Playbook). It defaults to original — except when uploaded_by is also present,
where it defaults to uploaded, so "what did this person upload last week" means
what you would expect.
curl --globoff "https://api.playbook.com/v1/my-org/ai_search?query=&filters[uploaded_by][]=asg_9f2c8a1b&filters[date_from]=2026-08-13" \
-H "Authorization: Bearer YOUR_TOKEN"
A person's name in query searches what is depicted in an asset, not who added it.
To filter by a person, always resolve them to a token first — see
People and Uploaders.
Pagination
AI Search uses cursor-based pagination for optimal performance.
First Page
curl "https://api.playbook.com/v1/my-org/ai_search?query=product+photography" \
-H "Authorization: Bearer YOUR_TOKEN"
Next Page
Use both parent_id and after_cursor from the previous response. The cursor is
scoped to that saved search; sending it without its parent starts a new first-page
query instead of continuing the existing one.
curl "https://api.playbook.com/v1/my-org/ai_search?query=product+photography&parent_id=search-token-123&after_cursor=0.85%402026-08-21T12%3A00%3A00Z%401" \
-H "Authorization: Bearer YOUR_TOKEN"
JavaScript Example
async function searchAllPages(query) {
const allResults = [];
let cursor = null;
let parentId = null;
while (true) {
const params = new URLSearchParams({
query: query
});
if (cursor) {
params.append('parent_id', parentId);
params.append('after_cursor', cursor);
}
const response = await fetch(
`https://api.playbook.com/v1/my-org/ai_search?${params}`,
{ headers: { Authorization: `Bearer ${ACCESS_TOKEN}` } }
);
const { data, pagy } = await response.json();
allResults.push(...data);
if (!pagy.has_next_page) break;
parentId = pagy.parent_id;
cursor = pagy.after_cursor;
}
return allResults;
}
Refinement with Parent Queries
Use parent queries to refine and narrow down search results:
Initial Search
curl "https://api.playbook.com/v1/my-org/ai_search?query=office+interior" \
-H "Authorization: Bearer YOUR_TOKEN"
Use the response's pagy.parent_id value as parent_id.
Refined Search
curl "https://api.playbook.com/v1/my-org/ai_search?query=modern+minimalist&parent_id=search-token-123" \
-H "Authorization: Bearer YOUR_TOKEN"
This narrows down the "office interior" results to only modern minimalist styles.
Use Cases
1. Visual Discovery
Find assets based on visual characteristics:
# By color palette
"images with warm autumn colors"
# By composition
"photos with central subject and blurred background"
# By style
"illustrations with flat design aesthetic"
2. Conceptual Search
Search by abstract concepts:
"assets that convey trust and professionalism"
"images that feel energetic and dynamic"
"designs with a vintage feel"
3. Smart Asset Discovery
Help users find assets they didn't know how to describe:
// User interface example
async function smartAssetFinder(userDescription) {
const response = await fetch(
`https://api.playbook.com/v1/${ORG_SLUG}/ai_search`,
{
method: 'GET',
headers: {
'Authorization': `Bearer ${ACCESS_TOKEN}`
},
params: {
query: userDescription,
filters: {
media_type: 'image/*',
tags: ['approved']
}
}
}
);
return await response.json();
}
// Usage
const results = await smartAssetFinder(
"bright cheerful images for kids product page"
);
4. Content Recommendations
Build "similar assets" features:
async function findSimilarAssets(assetToken) {
// First, get the asset details
const asset = await fetch(
`https://api.playbook.com/v1/${ORG_SLUG}/assets/${assetToken}`,
{ headers: { Authorization: `Bearer ${ACCESS_TOKEN}` } }
).then(r => r.json());
// Use AI search with asset characteristics
const query = `${asset.data.title} ${asset.data.tags.join(' ')}`;
const similar = await fetch(
`https://api.playbook.com/v1/${ORG_SLUG}/ai_search?query=${encodeURIComponent(query)}`,
{ headers: { Authorization: `Bearer ${ACCESS_TOKEN}` } }
).then(r => r.json());
// Filter out the original asset
return similar.data.filter(a => a.token !== assetToken);
}
Best Practices
1. Query Construction
Good Queries:
✅ "professional product photos with white background"
✅ "landscape images with mountains and blue sky"
✅ "minimalist logo designs in monochrome"
Less Effective:
❌ "good images" (too vague)
❌ "IMG_1234.jpg" (use regular search for filenames)
❌ "red" (too generic, be more specific)
2. Combining Search Types
Use AI Search for discovery, then refine with regular search:
async function hybridSearch(concept, exactTags) {
// Start with AI search for concept
const aiResults = await fetch(
`https://api.playbook.com/v1/${ORG_SLUG}/ai_search?query=${concept}`,
{ headers: { Authorization: `Bearer ${ACCESS_TOKEN}` } }
).then(r => r.json());
// Filter by exact tags
const filtered = aiResults.data.filter(asset =>
exactTags.every(tag => asset.tags.includes(tag))
);
return filtered;
}
// Usage
const results = await hybridSearch(
"corporate headshots with natural lighting",
["approved", "2024"]
);
3. Performance Optimization
Cache Common Searches:
const searchCache = new Map();
async function cachedAISearch(query, ttl = 3600000) {
const cacheKey = `ai_search:${query}`;
if (searchCache.has(cacheKey)) {
const cached = searchCache.get(cacheKey);
if (Date.now() - cached.timestamp < ttl) {
return cached.data;
}
}
const response = await fetch(
`https://api.playbook.com/v1/${ORG_SLUG}/ai_search?query=${encodeURIComponent(query)}`,
{ headers: { Authorization: `Bearer ${ACCESS_TOKEN}` } }
);
const data = await response.json();
searchCache.set(cacheKey, {
data,
timestamp: Date.now()
});
return data;
}
4. User Experience Tips
Provide Search Suggestions:
const searchSuggestions = [
"Product photos with clean backgrounds",
"Team photos in office settings",
"Lifestyle images with natural lighting",
"Brand assets in primary colors"
];
function showSearchHints() {
return searchSuggestions.map(suggestion => ({
label: suggestion,
onClick: () => performAISearch(suggestion)
}));
}
Complete Implementation Example
Here's a full search interface implementation:
class PlaybookAISearch {
constructor(orgSlug, accessToken) {
this.orgSlug = orgSlug;
this.accessToken = accessToken;
this.baseUrl = 'https://api.playbook.com/v1';
}
async search(query, options = {}) {
const params = new URLSearchParams({
query: query
});
// Add filters if provided
if (options.filters) {
if (options.filters.media_type) {
params.append('filters[media_type]', options.filters.media_type);
}
if (options.filters.boards) {
options.filters.boards.forEach(board => {
params.append('filters[boards][]', board);
});
}
if (options.filters.tags) {
options.filters.tags.forEach(tag => {
params.append('filters[tags][]', tag);
});
}
}
// A parent alone refines a search; a cursor must always travel with its parent.
if (options.after_cursor && !options.parent_id) {
throw new Error('after_cursor requires parent_id from the same response');
}
if (options.parent_id) {
params.append('parent_id', options.parent_id);
}
if (options.after_cursor) {
params.append('after_cursor', options.after_cursor);
}
const response = await fetch(
`${this.baseUrl}/${this.orgSlug}/ai_search?${params}`,
{ headers: { Authorization: `Bearer ${this.accessToken}` } }
);
if (!response.ok) {
throw new Error(`AI Search failed: ${response.statusText}`);
}
return await response.json();
}
async *searchStream(query, options = {}) {
let cursor = options.after_cursor ?? null;
let parentId = options.parent_id ?? null;
while (true) {
const results = await this.search(query, {
...options,
...(cursor ? { after_cursor: cursor, parent_id: parentId } : {}),
});
yield results.data;
if (!results.pagy.has_next_page) break;
parentId = results.pagy.parent_id;
cursor = results.pagy.after_cursor;
}
}
async searchAll(query, options = {}) {
const allResults = [];
for await (const batch of this.searchStream(query, options)) {
allResults.push(...batch);
}
return allResults;
}
}
// Usage
const search = new PlaybookAISearch('my-org', 'your_token');
// Simple search
const results = await search.search("sunset beach photos");
// Filtered search
const filtered = await search.search("product photography", {
filters: {
media_type: "image/jpeg",
tags: ["approved", "2024"]
}
});
// Get all results across pages
const allResults = await search.searchAll("office interiors");
// Stream results for progressive loading
for await (const batch of search.searchStream("brand assets")) {
console.log(`Loaded ${batch.length} assets`);
displayAssets(batch);
}
Error Handling
Common Errors
401: Unauthenticated
{
"error": "Unauthenticated"
}
Solution: Check your access token is valid.
403: AI Search not available
{
"error": "AI Search is not available for your organization"
}
Solution: Contact support to enable AI Search for your plan.
400: Invalid query
{
"error": "Query parameter is required"
}
Solution: Ensure the query parameter is not empty.
Comparison: When to Use Each Search Type
Use Regular Search When:
- Searching for specific filenames
- Looking up assets by exact tags
- Filtering by board membership
- You know the exact terms used in titles
Use AI Search When:
- Exploring your asset library
- Searching by visual concepts
- Finding assets for specific use cases
- Looking for aesthetically similar assets
- Users describe what they want in natural language
Use Both Together:
async function comprehensiveSearch(query, tags) {
// Get AI search results
const aiResults = await aiSearch(query);
// Filter by exact tags
const filtered = aiResults.filter(asset =>
tags.every(tag => asset.tags.includes(tag))
);
return filtered;
}
Related API Endpoints
Next Steps
- Learn about Asset Management to organize search results
- Explore Custom Fields for additional filtering
- Read about Webhooks to automate workflows based on search results