Skip to main content

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 ID
  • account (Object) - Account document
    • yext_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:

  1. Verify Account and Location

    if (!location || !account?.yext_status) {
    throw badRequest('Listings not initialized for this account.');
    }
  2. 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;
  3. 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 task
  • description - Task description and benefits
  • category - 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 completed
  • estimatedImpact - Expected impact on visibility and engagement (percentage)

Task Categories:

  • PHOTOS - Photo-related tasks
  • BUSINESS_INFO - Business information completion
  • REVIEWS - Review management tasks
  • POSTS - Social posts and updates
  • DESCRIPTIONS - Business descriptions
  • PRODUCTS_SERVICES - Products and services information
  • OTHER - 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

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 ID
  • account (Object) - Account document
    • yext_status (Boolean) - Service activation status
  • redirect_url (String) - URL to redirect after task completion
  • taskIds (String, optional) - Comma-separated task IDs to filter

Returns: Promise<String> - Complete URL with redirect parameter appended

Business Logic Flow:

  1. Validate Parameters

    if (!location || !account?.yext_status) {
    throw badRequest('Listings not initialized for this account.');
    }

    if (!redirect_url) {
    throw badRequest('Redirect URL is required.');
    }
  2. 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;
  3. 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

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โ€‹

  1. Requires yext_status: Account must have yext_status: true field
  2. Entity Eligibility: Not all entities have optimization tasks
  3. Time-Limited Links: Optimization links expire (generate fresh)
  4. Single Use: Links typically single-use tokens
  5. Redirect Required: Must provide redirect_url for user return flow
  6. continueUrl Appended: Service automatically appends redirect as continueUrl parameter
  7. Task Categories: Group tasks by category for better UX
  8. Impact Metrics: Use estimatedImpact to prioritize tasks
  9. Status Tracking: Tasks have status (AVAILABLE, IN_PROGRESS, COMPLETED)
  10. Empty Response Error: Throws error instead of returning empty array
  11. Comma-Separated: Multiple taskIds separated by commas
  12. Optional Filtering: taskIds parameter is optional

๐ŸŽฏ 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',
};
}
๐Ÿ’ฌ

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:31 AM