Skip to main content

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 parameters
    • limit (Number, optional) - Maximum publishers to return
    • offset (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:

  1. Verify Account Initialization

    const account = await accountService.find(accountId);

    if (account?.error) {
    throw notFound(account?.message);
    }
  2. 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 name
  • url - Publisher website URL
  • favicon - Publisher favicon/logo URL
  • category - Publisher category (SEARCH_ENGINES, SOCIAL_NETWORKS, REVIEW_SITES, etc.)
  • countries - Available countries (array or "GLOBAL")
  • hasOptIn - Whether publisher supports opt-in/opt-out
  • requiresPublisherAuthorization - 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 Assistant
  • AUTOMOTIVE - Car-specific directories
  • REAL_ESTATE - Real estate platforms
  • TRAVEL - Travel booking sites
  • OTHER - 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: true means entity can opt-in/out via entityOpt() 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โ€‹

  1. Read-Only: This service only retrieves publishers, doesn't create/modify
  2. Account Required: Must have entity initialized even though entity ID not directly used
  3. Static Data: Publisher list changes infrequently (new publishers added rarely)
  4. Cache Aggressively: Consider 7-day cache for publisher list
  5. 150+ Publishers: Returns large list, consider pagination for UI
  6. Opt-In Support: Not all publishers support opt-in/out (hasOptIn field)
  7. Authorization: Some publishers require setup in Yext dashboard
  8. Global Coverage: Many publishers available worldwide, check countries array
  9. Categories: Use category field to group publishers in UI
  10. Favicons: Use favicon for publisher logos in UI
  11. Publisher IDs: Use id field for opt-in/out operations in entities service

๐ŸŽฏ 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`);
๐Ÿ’ฌ

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