Yext Optimization Service
๐ Overviewโ
The Yext Optimization service manages listing optimization tasks that help improve business listing visibility and quality across publisher directories. Optimization tasks include adding photos, completing business information, responding to reviews, and other actions that enhance listing performance. The service provides both task lists and embeddable links for users to complete tasks directly in Yext's interface.
Source File: external/Integrations/Yext/services/optimization.service.js
External API: Yext Optimization Tasks API
Primary Use: Retrieve optimization tasks and generate task completion links
๐๏ธ Collections Usedโ
accountโ
- Operations: Read
- Model:
external/models/account.js - Usage Context: Verify account has Yext service activated
- Key Fields:
yext_status- Service activation status
๐ Data Flowโ
sequenceDiagram
participant Client
participant OptimizationService
participant AccountDB
participant YextAPI
Client->>OptimizationService: optimizationTasks(location, account)
alt Account has yext_status
OptimizationService->>YextAPI: GET /optimizationtasks
Note over OptimizationService,YextAPI: Headers: api-key<br/>Params: locationIds, taskIds
YextAPI-->>OptimizationService: Tasks array
alt Tasks exist
OptimizationService-->>Client: Return tasks
else No tasks
OptimizationService-->>Client: 400 Error (not eligible)
end
else No yext_status
OptimizationService-->>Client: 400 Error (not initialized)
end
Client->>OptimizationService: optimizationTasksLink(location, redirect_url)
OptimizationService->>YextAPI: GET /optimizationlink
YextAPI-->>OptimizationService: Task completion link
OptimizationService->>OptimizationService: Append redirect URL
OptimizationService-->>Client: Return complete link
style OptimizationService fill:#e3f2fd
style YextAPI fill:#fff4e6
๐ง Business Logic & Functionsโ
optimizationTasks({ location, account, taskIds = '' })โ
Purpose: Retrieve available optimization tasks for a business location
Source: services/optimization.service.js
External API Endpoint: GET https://api.yext.com/v2/accounts/me/optimizationtasks
Parameters:
location(String) - Yext entity/location IDaccount(Object) - Account documentyext_status(Boolean) - Service activation status
taskIds(String, optional) - Comma-separated task IDs to filter
Returns: Promise<Array> - Array of optimization task objects
Business Logic Flow:
-
Verify Account and Location
if (!location || !account?.yext_status) {
throw badRequest('Listings not initialized for this account.');
} -
Fetch Optimization Tasks
const v = process.env.YEXT_API_VPARAM || '20200525';
const url = 'https://api.yext.com/v2/accounts/me/optimizationtasks';
const options = {
method: 'GET',
headers: { 'api-key': process.env.YEXT_API_KEYS },
params: { v: v, locationIds: location, taskIds },
url,
};
const dns = await axios(options);
const optimization_tasks = dns.data; -
Validate Tasks Exist
if (optimization_tasks.response.optimizationTasks.length) {
return optimization_tasks.response.optimizationTasks;
} else {
throw badRequest('User not eligible for the optimization task(s).');
}
API Request Example:
GET https://api.yext.com/v2/accounts/me/optimizationtasks?v=20200525&locationIds=12345678
Headers:
api-key: {YEXT_API_KEY}
API Response Example:
{
"meta": {
"uuid": "abc-def-123",
"errors": []
},
"response": {
"optimizationTasks": [
{
"taskId": "ADD_PHOTOS",
"name": "Add Photos",
"description": "Add photos to your listing to increase engagement",
"category": "PHOTOS",
"priority": "HIGH",
"status": "AVAILABLE",
"completionEligible": true,
"estimatedImpact": {
"visibility": 15,
"engagement": 25
}
},
{
"taskId": "ADD_HOURS",
"name": "Add Business Hours",
"description": "Complete your business hours for better customer experience",
"category": "BUSINESS_INFO",
"priority": "MEDIUM",
"status": "AVAILABLE",
"completionEligible": true,
"estimatedImpact": {
"visibility": 10,
"engagement": 15
}
},
{
"taskId": "RESPOND_TO_REVIEWS",
"name": "Respond to Reviews",
"description": "Respond to recent customer reviews",
"category": "REVIEWS",
"priority": "HIGH",
"status": "AVAILABLE",
"completionEligible": true,
"estimatedImpact": {
"visibility": 5,
"engagement": 20
}
}
]
}
}
Task Structure:
taskId- Unique task identifier (e.g., "ADD_PHOTOS", "ADD_HOURS")name- Display name for the taskdescription- Task description and benefitscategory- Task category (PHOTOS, BUSINESS_INFO, REVIEWS, etc.)priority- Task priority (HIGH, MEDIUM, LOW)status- Task status (AVAILABLE, IN_PROGRESS, COMPLETED)completionEligible- Whether task can be completedestimatedImpact- Expected impact on visibility and engagement (percentage)
Task Categories:
PHOTOS- Photo-related tasksBUSINESS_INFO- Business information completionREVIEWS- Review management tasksPOSTS- Social posts and updatesDESCRIPTIONS- Business descriptionsPRODUCTS_SERVICES- Products and services informationOTHER- Miscellaneous tasks
Error Handling:
- Not Initialized: Throws 400 Bad Request if no location or yext_status
- No Tasks: Throws 400 Bad Request if entity has no eligible tasks
- API Errors: Yext API errors propagate up
Example Usage:
// Get all optimization tasks for entity
const tasks = await optimizationService.optimizationTasks({
location: '12345678',
account: { yext_status: true },
taskIds: '', // All tasks
});
console.log('Available tasks:', tasks.length);
// Group by priority
const highPriority = tasks.filter(t => t.priority === 'HIGH');
console.log('High priority tasks:', highPriority);
// Calculate total impact
const totalImpact = tasks.reduce((sum, task) => {
return sum + (task.estimatedImpact?.visibility || 0);
}, 0);
console.log('Potential visibility increase:', totalImpact + '%');
Filter Specific Tasks:
// Get only photo-related tasks
const photoTasks = await optimizationService.optimizationTasks({
location: '12345678',
account: { yext_status: true },
taskIds: 'ADD_PHOTOS,UPDATE_PHOTOS',
});
Key Business Rules:
- Requires yext_status: Account must have Yext service activated
- Empty Array Error: Throws error if no tasks available
- Location Required: Must provide entity/location ID
- Comma-Separated IDs: Multiple task IDs separated by commas
optimizationTasksLink({ location, account, redirect_url, taskIds })โ
Purpose: Generate embeddable link for user to complete optimization tasks in Yext interface
Source: services/optimization.service.js
External API Endpoint: GET https://api.yext.com/v2/accounts/me/optimizationlink
Parameters:
location(String) - Yext entity/location IDaccount(Object) - Account documentyext_status(Boolean) - Service activation status
redirect_url(String) - URL to redirect after task completiontaskIds(String, optional) - Comma-separated task IDs to filter
Returns: Promise<String> - Complete URL with redirect parameter appended
Business Logic Flow:
-
Validate Parameters
if (!location || !account?.yext_status) {
throw badRequest('Listings not initialized for this account.');
}
if (!redirect_url) {
throw badRequest('Redirect URL is required.');
} -
Fetch Optimization Link
const v = process.env.YEXT_API_VPARAM || '20200525';
const url = 'https://api.yext.com/v2/accounts/me/optimizationlink';
const options = {
method: 'GET',
headers: { 'api-key': process.env.YEXT_API_KEYS },
params: { v: v, locationId: location, ...(taskIds && { taskIds }) },
url,
};
const dns = await axios(options);
const optimization_tasks_link = dns.data; -
Append Redirect URL
if (optimization_tasks_link.response.link) {
return `${optimization_tasks_link.response.link}&continueUrl=${redirect_url}`;
} else {
throw badRequest('User not eligible for the optimization task(s).');
}
API Request Example:
GET https://api.yext.com/v2/accounts/me/optimizationlink?v=20200525&locationId=12345678&taskIds=ADD_PHOTOS
Headers:
api-key: {YEXT_API_KEY}
API Response Example:
{
"meta": {
"uuid": "abc-def-123",
"errors": []
},
"response": {
"link": "https://www.yext.com/s/12345678/optimize?token=xyz789&tasks=ADD_PHOTOS"
}
}
Final Returned URL:
https://www.yext.com/s/12345678/optimize?token=xyz789&tasks=ADD_PHOTOS&continueUrl=https://app.dashclicks.com/listings/success
Error Handling:
- Not Initialized: Throws 400 Bad Request if no location or yext_status
- Missing Redirect URL: Throws 400 Bad Request if redirect_url not provided
- No Link: Throws 400 Bad Request if Yext doesn't return link
- API Errors: Yext API errors propagate up
Example Usage:
// Generate link for user to complete optimization tasks
const link = await optimizationService.optimizationTasksLink({
location: '12345678',
account: { yext_status: true },
redirect_url: 'https://app.dashclicks.com/listings/complete',
taskIds: 'ADD_PHOTOS,ADD_HOURS', // Optional: specific tasks
});
console.log('Optimization link:', link);
// Open link in iframe or new window
window.open(link, '_blank');
Complete Workflow:
// 1. Get available tasks
const tasks = await optimizationService.optimizationTasks({
location: entityId,
account: account,
});
// 2. Let user select tasks to complete
const selectedTaskIds = tasks
.filter(t => userSelection.includes(t.taskId))
.map(t => t.taskId)
.join(',');
// 3. Generate link for selected tasks
const link = await optimizationService.optimizationTasksLink({
location: entityId,
account: account,
redirect_url: 'https://app.dashclicks.com/listings/optimized',
taskIds: selectedTaskIds,
});
// 4. Redirect user to complete tasks
window.location.href = link;
Key Business Rules:
- Redirect Required: Must provide redirect_url for user return
- Appends continueUrl: Automatically appends redirect as continueUrl parameter
- Time-Limited: Link expires after period (Yext determines expiration)
- Task Filtering: Can filter to specific tasks or show all
- Single Use: Links typically single-use tokens
- Requires yext_status: Account must have service activated
๐ Integration Pointsโ
Optimization Dashboardโ
// Build optimization dashboard with tasks and completion link
async function buildOptimizationDashboard(entityId, account) {
// 1. Get all optimization tasks
const tasks = await optimizationService.optimizationTasks({
location: entityId,
account: account,
});
// 2. Calculate impact metrics
const totalVisibilityImpact = tasks.reduce(
(sum, t) => sum + (t.estimatedImpact?.visibility || 0),
0,
);
const totalEngagementImpact = tasks.reduce(
(sum, t) => sum + (t.estimatedImpact?.engagement || 0),
0,
);
// 3. Group by category
const byCategory = tasks.reduce((acc, task) => {
if (!acc[task.category]) {
acc[task.category] = [];
}
acc[task.category].push(task);
return acc;
}, {});
// 4. Generate completion link
const completionLink = await optimizationService.optimizationTasksLink({
location: entityId,
account: account,
redirect_url: 'https://app.dashclicks.com/listings/optimized',
});
return {
tasks: tasks,
taskCount: tasks.length,
impact: {
visibility: totalVisibilityImpact,
engagement: totalEngagementImpact,
},
byCategory: byCategory,
completionLink: completionLink,
};
}
Priority Tasks Widgetโ
// Show high-priority tasks only
async function getHighPriorityTasks(entityId, account) {
const tasks = await optimizationService.optimizationTasks({
location: entityId,
account: account,
});
return tasks
.filter(t => t.priority === 'HIGH')
.sort((a, b) => {
// Sort by visibility impact
return (b.estimatedImpact?.visibility || 0) - (a.estimatedImpact?.visibility || 0);
});
}
Task Completion Trackingโ
// Track which tasks have been completed over time
async function trackTaskCompletion(entityId, account) {
const tasks = await optimizationService.optimizationTasks({
location: entityId,
account: account,
});
const completed = tasks.filter(t => t.status === 'COMPLETED');
const available = tasks.filter(t => t.status === 'AVAILABLE');
const inProgress = tasks.filter(t => t.status === 'IN_PROGRESS');
return {
total: tasks.length,
completed: completed.length,
available: available.length,
inProgress: inProgress.length,
completionRate: (completed.length / tasks.length) * 100,
};
}
๐งช Edge Cases & Special Handlingโ
No Optimization Tasks Availableโ
Issue: Entity has no eligible optimization tasks
Handling: Throws 400 Bad Request
if (optimization_tasks.response.optimizationTasks.length) {
return optimization_tasks.response.optimizationTasks;
} else {
throw badRequest('User not eligible for the optimization task(s).');
}
Reasons:
- All tasks already completed
- Entity doesn't qualify for optimization program
- No active optimization tasks for entity's category
Account Not Activatedโ
Issue: Account doesn't have yext_status set
Handling: Throws 400 Bad Request
if (!location || !account?.yext_status) {
throw badRequest('Listings not initialized for this account.');
}
Solution: Activate Yext service first
Missing Redirect URLโ
Issue: No redirect_url provided to optimizationTasksLink()
Handling: Throws 400 Bad Request
if (!redirect_url) {
throw badRequest('Redirect URL is required.');
}
Why: User needs return path after completing tasks
Link Expirationโ
Issue: Optimization link expires after period
Handling: Generate new link when needed
Recommendation: Generate link on-demand, don't store
Task ID Filteringโ
Issue: Specific task IDs requested but not available
Handling: Returns empty array or error from Yext
Solution: Get all tasks first, validate IDs exist
โ ๏ธ Important Notesโ
- Requires yext_status: Account must have
yext_status: truefield - Entity Eligibility: Not all entities have optimization tasks
- Time-Limited Links: Optimization links expire (generate fresh)
- Single Use: Links typically single-use tokens
- Redirect Required: Must provide redirect_url for user return flow
- continueUrl Appended: Service automatically appends redirect as continueUrl parameter
- Task Categories: Group tasks by category for better UX
- Impact Metrics: Use estimatedImpact to prioritize tasks
- Status Tracking: Tasks have status (AVAILABLE, IN_PROGRESS, COMPLETED)
- Empty Response Error: Throws error instead of returning empty array
- Comma-Separated: Multiple taskIds separated by commas
- Optional Filtering: taskIds parameter is optional
๐ Related Documentationโ
- Yext Integration Overview: index.md
- Entities Service: entities.md - Entity management
- Account Service: account.md - Account verification
- Yext Optimization API: https://developer.yext.com/docs/api-reference/optimization/
๐ฏ Complete Integration Exampleโ
// Complete optimization flow
async function implementOptimizationFlow(accountId, entityId) {
// 1. Get account
const account = await Account.findById(accountId);
if (!account.yext_status) {
throw new Error('Yext service not activated');
}
// 2. Check for optimization tasks
let tasks;
try {
tasks = await optimizationService.optimizationTasks({
location: entityId,
account: account,
});
} catch (error) {
if (error.message.includes('not eligible')) {
return {
hasOptimizations: false,
message: 'No optimization tasks available',
};
}
throw error;
}
// 3. Analyze tasks
const highPriority = tasks.filter(t => t.priority === 'HIGH');
const totalImpact = tasks.reduce((sum, t) => sum + (t.estimatedImpact?.visibility || 0), 0);
// 4. Generate completion link
const link = await optimizationService.optimizationTasksLink({
location: entityId,
account: account,
redirect_url: 'https://app.dashclicks.com/listings/complete',
});
// 5. Return optimization data
return {
hasOptimizations: true,
tasks: tasks,
taskCount: tasks.length,
highPriorityCount: highPriority.length,
estimatedImpact: totalImpact,
completionLink: link,
recommendation:
highPriority.length > 0
? 'Complete high-priority tasks first for maximum impact'
: 'Complete any available tasks to improve visibility',
};
}