Yext Publishers Service
๐ Overviewโ
The Yext Publishers service retrieves the list of available publisher directories where business listings can be distributed. Publishers include major platforms like Google, Facebook, Yelp, Bing, Apple Maps, and 150+ other directories. This service is used to display available distribution platforms and check listing coverage.
Source File: external/Integrations/Yext/services/publishers.service.js
External API: Yext Listings Publishers API
Primary Use: List available publisher directories for listing distribution
๐๏ธ Collections Usedโ
accountโ
- Operations: Read (via account service)
- Model:
external/models/account.js - Usage Context: Verify account has entity initialized before API call
๐ Data Flowโ
sequenceDiagram
participant Client
participant PublishersService
participant AccountService
participant AccountDB
participant YextAPI
Client->>PublishersService: get(accountId, query)
PublishersService->>AccountService: find(accountId)
AccountService->>AccountDB: Check entity initialization
alt Entity initialized
AccountDB-->>AccountService: Return entity ID
AccountService-->>PublishersService: Entity ID
PublishersService->>YextAPI: GET /listings/publishers
YextAPI-->>PublishersService: Publishers list
PublishersService-->>Client: Return publishers
else Not initialized
AccountDB-->>AccountService: Error object
AccountService-->>PublishersService: Error
PublishersService-->>Client: 404 Not Found
end
style PublishersService fill:#e3f2fd
style YextAPI fill:#fff4e6
๐ง Business Logic & Functionsโ
get({ query, body, accountId })โ
Purpose: Retrieve list of available publisher directories from Yext
Source: services/publishers.service.js
External API Endpoint: GET https://api.yext.com/v2/accounts/me/listings/publishers
Parameters:
query(Object) - Query parameterslimit(Number, optional) - Maximum publishers to returnoffset(Number, optional) - Offset for pagination
body(Object) - Request body (unused in GET request)accountId(ObjectId) - DashClicks account ID
Returns: Promise<Object> - Publishers data from Yext
Business Logic Flow:
-
Verify Account Initialization
const account = await accountService.find(accountId);
if (account?.error) {
throw notFound(account?.message);
} -
Fetch Publishers from Yext
const v = process.env.YEXT_API_VPARAM || '20200525';
const url =
'https://api.yext.com/v2/accounts/me/listings/publishers?' + querystring.encode({ v: v });
const options = {
method: 'GET',
headers: { 'api-key': process.env.YEXT_API_KEYS },
data: body,
params: query,
url,
};
const dns = await axios(options);
return dns.data;
API Request Example:
GET https://api.yext.com/v2/accounts/me/listings/publishers?v=20200525&limit=100&offset=0
Headers:
api-key: {YEXT_API_KEY}
API Response Example:
{
"meta": {
"uuid": "abc-def-123",
"errors": []
},
"response": {
"count": 150,
"publishers": [
{
"id": "123",
"name": "Google",
"url": "https://www.google.com",
"favicon": "https://www.google.com/favicon.ico",
"category": "SEARCH_ENGINES",
"countries": ["US", "CA", "GB", "AU"],
"hasOptIn": true,
"requiresPublisherAuthorization": false
},
{
"id": "456",
"name": "Facebook",
"url": "https://www.facebook.com",
"favicon": "https://www.facebook.com/favicon.ico",
"category": "SOCIAL_NETWORKS",
"countries": ["GLOBAL"],
"hasOptIn": true,
"requiresPublisherAuthorization": true
},
{
"id": "789",
"name": "Yelp",
"url": "https://www.yelp.com",
"favicon": "https://www.yelp.com/favicon.ico",
"category": "REVIEW_SITES",
"countries": ["US", "CA"],
"hasOptIn": true,
"requiresPublisherAuthorization": false
},
{
"id": "101",
"name": "Bing",
"url": "https://www.bing.com",
"favicon": "https://www.bing.com/favicon.ico",
"category": "SEARCH_ENGINES",
"countries": ["GLOBAL"],
"hasOptIn": false,
"requiresPublisherAuthorization": false
}
// ... 146+ more publishers
]
}
}
Publisher Structure:
id- Unique publisher identifier (used for opt-in/out)name- Publisher display nameurl- Publisher website URLfavicon- Publisher favicon/logo URLcategory- Publisher category (SEARCH_ENGINES, SOCIAL_NETWORKS, REVIEW_SITES, etc.)countries- Available countries (array or "GLOBAL")hasOptIn- Whether publisher supports opt-in/opt-outrequiresPublisherAuthorization- Whether publisher requires additional authorization
Publisher Categories:
SEARCH_ENGINES- Google, Bing, Yahoo, etc.SOCIAL_NETWORKS- Facebook, Instagram, Twitter, etc.REVIEW_SITES- Yelp, TripAdvisor, Trustpilot, etc.MAPPING_SERVICES- Apple Maps, Waze, TomTom, etc.DIRECTORIES- YellowPages, Whitepages, Local.com, etc.VOICE_ASSISTANTS- Alexa, Siri, Google AssistantAUTOMOTIVE- Car-specific directoriesREAL_ESTATE- Real estate platformsTRAVEL- Travel booking sitesOTHER- Miscellaneous publishers
Error Handling:
- Account Not Initialized: Throws 404 Not Found with account service message
- API Errors: Yext API errors propagate up
Example Usage:
// Get all publishers
const publishers = await publishersService.get({
accountId: '507f1f77bcf86cd799439011',
query: { v: '20200525' },
body: {},
});
console.log('Total publishers:', publishers.response.count);
console.log('Publishers:', publishers.response.publishers);
// Filter by category
const searchEngines = publishers.response.publishers.filter(
pub => pub.category === 'SEARCH_ENGINES',
);
console.log(
'Search engines:',
searchEngines.map(p => p.name),
);
// Check which require authorization
const requiresAuth = publishers.response.publishers.filter(
pub => pub.requiresPublisherAuthorization,
);
console.log(
'Require authorization:',
requiresAuth.map(p => p.name),
);
Pagination:
// Get first 50 publishers
const page1 = await publishersService.get({
accountId: accountId,
query: { v: '20200525', limit: 50, offset: 0 },
body: {},
});
// Get next 50 publishers
const page2 = await publishersService.get({
accountId: accountId,
query: { v: '20200525', limit: 50, offset: 50 },
body: {},
});
Key Business Rules:
- Account Verification: Requires account to have entity initialized
- Read-Only: Only retrieves publishers, doesn't modify
- Static Data: Publisher list changes infrequently, suitable for caching
- Opt-In Support:
hasOptIn: truemeans entity can opt-in/out viaentityOpt()in entities service - Authorization: Some publishers require additional setup via Yext dashboard
๐ Integration Pointsโ
Display Publisher Coverageโ
// Show which publishers entity is listed on
async function getEntityPublisherCoverage(accountId, entityId) {
// 1. Get all available publishers
const publishersResponse = await publishersService.get({
accountId: accountId,
query: { v: '20200525' },
body: {},
});
// 2. Get entity's active listings
const listingsResponse = await entitiesService.listListingsByLocation({
accountId: accountId,
params: { locationId: entityId, v: '20200525' },
});
// 3. Match publishers to listings
const publishers = publishersResponse.response.publishers;
const listings = listingsResponse.response.listings;
const coverage = publishers.map(pub => {
const listing = listings.find(l => l.publisherId === pub.id);
return {
publisherId: pub.id,
name: pub.name,
category: pub.category,
status: listing ? listing.status : 'NOT_LISTED',
listingUrl: listing?.listingUrl || null,
};
});
return coverage;
}
// Usage
const coverage = await getEntityPublisherCoverage(accountId, entityId);
const live = coverage.filter(c => c.status === 'LIVE');
console.log(`Live on ${live.length} publishers`);
Publisher Filtering UIโ
// Filter publishers for UI display
function filterPublishers(publishers, filters) {
let filtered = publishers;
// Filter by category
if (filters.category) {
filtered = filtered.filter(pub => pub.category === filters.category);
}
// Filter by country
if (filters.country) {
filtered = filtered.filter(pub => {
return pub.countries.includes('GLOBAL') || pub.countries.includes(filters.country);
});
}
// Filter by opt-in support
if (filters.hasOptIn !== undefined) {
filtered = filtered.filter(pub => pub.hasOptIn === filters.hasOptIn);
}
// Filter by name search
if (filters.search) {
const term = filters.search.toLowerCase();
filtered = filtered.filter(pub => pub.name.toLowerCase().includes(term));
}
return filtered;
}
// Usage
const publishers = await publishersService.get({
accountId: accountId,
query: { v: '20200525' },
body: {},
});
// Get only US search engines
const usSearchEngines = filterPublishers(publishers.response.publishers, {
category: 'SEARCH_ENGINES',
country: 'US',
});
Publisher Logo Displayโ
// Get publisher logos for UI
async function getPublisherLogos(accountId) {
const publishers = await publishersService.get({
accountId: accountId,
query: { v: '20200525' },
body: {},
});
return publishers.response.publishers.map(pub => ({
id: pub.id,
name: pub.name,
logo: pub.favicon || '/default-publisher-logo.png',
}));
}
Major Publishers Listโ
// Get list of major publishers only
const MAJOR_PUBLISHERS = [
'Google',
'Facebook',
'Yelp',
'Bing',
'Apple Maps',
'Yahoo',
'Foursquare',
'TripAdvisor',
];
function getMajorPublishers(publishers) {
return publishers.filter(pub => MAJOR_PUBLISHERS.includes(pub.name));
}
// Usage
const allPublishers = await publishersService.get({
accountId: accountId,
query: { v: '20200525' },
body: {},
});
const major = getMajorPublishers(allPublishers.response.publishers);
console.log('Major publishers:', major);
๐งช Edge Cases & Special Handlingโ
Account Not Initializedโ
Issue: Account doesn't have Yext entity
Handling: Throws 404 Not Found
const account = await accountService.find(accountId);
if (account?.error) {
throw notFound(account?.message);
}
Solution: Create entity first using entitiesService.create()
Large Publisher Listโ
Issue: Yext returns 150+ publishers
Handling: Use pagination or cache entire list
Recommendation: Cache publishers list aggressively
let publishersCache = null;
let cacheTimestamp = null;
const CACHE_TTL = 7 * 24 * 60 * 60 * 1000; // 7 days
async function getPublishersWithCache(accountId) {
const now = Date.now();
if (publishersCache && cacheTimestamp && now - cacheTimestamp < CACHE_TTL) {
return publishersCache;
}
const publishers = await publishersService.get({
accountId: accountId,
query: { v: '20200525' },
body: {},
});
publishersCache = publishers;
cacheTimestamp = now;
return publishers;
}
Missing Faviconsโ
Issue: Some publishers may not have favicon URLs
Handling: Check for null/undefined favicon
const logo = pub.favicon || '/default-logo.png';
Global vs Country-Specificโ
Issue: Some publishers are global, others country-specific
Handling: Check countries array
function isAvailableInCountry(publisher, countryCode) {
return publisher.countries.includes('GLOBAL') || publisher.countries.includes(countryCode);
}
Authorization Requiredโ
Issue: Some publishers require additional authorization
Handling: Check requiresPublisherAuthorization flag
if (publisher.requiresPublisherAuthorization) {
console.log(`${publisher.name} requires additional authorization in Yext dashboard`);
}
โ ๏ธ Important Notesโ
- Read-Only: This service only retrieves publishers, doesn't create/modify
- Account Required: Must have entity initialized even though entity ID not directly used
- Static Data: Publisher list changes infrequently (new publishers added rarely)
- Cache Aggressively: Consider 7-day cache for publisher list
- 150+ Publishers: Returns large list, consider pagination for UI
- Opt-In Support: Not all publishers support opt-in/out (
hasOptInfield) - Authorization: Some publishers require setup in Yext dashboard
- Global Coverage: Many publishers available worldwide, check
countriesarray - Categories: Use
categoryfield to group publishers in UI - Favicons: Use
faviconfor publisher logos in UI - Publisher IDs: Use
idfield for opt-in/out operations in entities service
๐ Related Documentationโ
- Yext Integration Overview: index.md
- Entities Service: entities.md - Uses publisher IDs for opt-in/out
- Account Service: account.md - Verifies account initialization
- Yext Publishers API: https://developer.yext.com/docs/api-reference/listings-publishers/
๐ฏ Common Use Casesโ
1. Show Available Publishersโ
// Display all publishers in UI
const publishers = await publishersService.get({
accountId: accountId,
query: { v: '20200525' },
body: {},
});
// Group by category
const grouped = publishers.response.publishers.reduce((acc, pub) => {
if (!acc[pub.category]) {
acc[pub.category] = [];
}
acc[pub.category].push(pub);
return acc;
}, {});
console.log('Search Engines:', grouped.SEARCH_ENGINES.length);
console.log('Social Networks:', grouped.SOCIAL_NETWORKS.length);
console.log('Review Sites:', grouped.REVIEW_SITES.length);
2. Publisher Selection for Opt-Inโ
// Show publishers user can opt-in to
const publishers = await publishersService.get({
accountId: accountId,
query: { v: '20200525' },
body: {},
});
const optInAvailable = publishers.response.publishers.filter(pub => pub.hasOptIn);
console.log('Publishers supporting opt-in:', optInAvailable.length);
3. Country-Specific Publishersโ
// Get publishers available in specific country
function getPublishersForCountry(publishers, countryCode) {
return publishers.filter(pub => {
return pub.countries.includes('GLOBAL') || pub.countries.includes(countryCode);
});
}
const publishers = await publishersService.get({
accountId: accountId,
query: { v: '20200525' },
body: {},
});
const usPublishers = getPublishersForCountry(publishers.response.publishers, 'US');
const ukPublishers = getPublishersForCountry(publishers.response.publishers, 'GB');
console.log('US publishers:', usPublishers.length);
console.log('UK publishers:', ukPublishers.length);
4. Major Publishers Checklistโ
// Create checklist of major publisher coverage
async function getMajorPublisherStatus(accountId, entityId) {
const MAJOR = ['Google', 'Facebook', 'Yelp', 'Bing', 'Apple Maps'];
const publishers = await publishersService.get({
accountId: accountId,
query: { v: '20200525' },
body: {},
});
const listings = await entitiesService.listListingsByLocation({
accountId: accountId,
params: { locationId: entityId, v: '20200525' },
});
return MAJOR.map(name => {
const pub = publishers.response.publishers.find(p => p.name === name);
const listing = listings.response.listings.find(l => l.publisherId === pub?.id);
return {
name: name,
status: listing ? listing.status : 'NOT_LISTED',
live: listing?.status === 'LIVE',
};
});
}
// Usage
const status = await getMajorPublisherStatus(accountId, entityId);
const liveCount = status.filter(s => s.live).length;
console.log(`Live on ${liveCount}/${status.length} major publishers`);