This is page 2 of 3. Use http://codebase.md/dataforseo/mcp-server-typescript?lines=false&page={x} to view the full context. # Directory Structure ``` ├── .dockerignore ├── .gitignore ├── Dockerfile ├── field-config.example.json ├── LICENSE ├── package.json ├── README.md ├── scripts │ └── generate-worker-version.cjs ├── src │ ├── core │ │ ├── client │ │ │ └── dataforseo.client.ts │ │ ├── config │ │ │ ├── field-configuration.ts │ │ │ ├── global.tool.ts │ │ │ └── modules.config.ts │ │ ├── modules │ │ │ ├── ai-optimization │ │ │ │ ├── ai-optimization-api-module.ts │ │ │ │ └── tools │ │ │ │ └── keyword-data │ │ │ │ ├── ai-optimization-keyword-data-locations-and-languages.ts │ │ │ │ └── ai-optimization-keyword-data-search-volume.ts │ │ │ ├── backlinks │ │ │ │ ├── backlinks-api.module.ts │ │ │ │ ├── backlinks.prompt.ts │ │ │ │ └── tools │ │ │ │ ├── backlinks-anchor.tool.ts │ │ │ │ ├── backlinks-backlinks.tool.ts │ │ │ │ ├── backlinks-bulk-backlinks.tool.ts │ │ │ │ ├── backlinks-bulk-new-lost-backlinks.tool.ts │ │ │ │ ├── backlinks-bulk-new-lost-referring-domains.tool.ts │ │ │ │ ├── backlinks-bulk-pages-summary.ts │ │ │ │ ├── backlinks-bulk-ranks.tool.ts │ │ │ │ ├── backlinks-bulk-referring-domains.tool.ts │ │ │ │ ├── backlinks-bulk-spam-score.tool.ts │ │ │ │ ├── backlinks-bulk-spam-score.ts │ │ │ │ ├── backlinks-competitors.tool.ts │ │ │ │ ├── backlinks-domain-intersection.tool.ts │ │ │ │ ├── backlinks-domain-pages-summary.tool.ts │ │ │ │ ├── backlinks-domain-pages.tool.ts │ │ │ │ ├── backlinks-filters.tool.ts │ │ │ │ ├── backlinks-page-intersection.tool.ts │ │ │ │ ├── backlinks-referring-domains.tool.ts │ │ │ │ ├── backlinks-referring-networks.tool.ts │ │ │ │ ├── backlinks-summary.tool.ts │ │ │ │ ├── backlinks-timeseries-new-lost-summary.tool.ts │ │ │ │ └── backlinks-timeseries-summary.tool.ts │ │ │ ├── base.module.ts │ │ │ ├── base.tool.ts │ │ │ ├── business-data-api │ │ │ │ ├── business-data-api.module.ts │ │ │ │ └── tools │ │ │ │ └── listings │ │ │ │ ├── business-listings-filters.tool.ts │ │ │ │ └── business-listings-search.tool.ts │ │ │ ├── content-analysis │ │ │ │ ├── content-analysis-api.module.ts │ │ │ │ └── tools │ │ │ │ ├── content-analysis-phrase-trends.ts │ │ │ │ ├── content-analysis-search.tool.ts │ │ │ │ └── content-analysis-summary.ts │ │ │ ├── dataforseo-labs │ │ │ │ ├── dataforseo-labs-api.module.ts │ │ │ │ ├── dataforseo-labs.prompts.ts │ │ │ │ └── tools │ │ │ │ ├── google │ │ │ │ │ ├── competitor-research │ │ │ │ │ │ ├── google-bulk-traffic-estimation.tool.ts │ │ │ │ │ │ ├── google-domain-competitors.tool.ts │ │ │ │ │ │ ├── google-domain-intersection.tool.ts │ │ │ │ │ │ ├── google-domain-rank-overview.tool.ts │ │ │ │ │ │ ├── google-historical-domain-rank-overview.tool.ts │ │ │ │ │ │ ├── google-historical-serp.ts │ │ │ │ │ │ ├── google-page-intersection.tool.ts │ │ │ │ │ │ ├── google-ranked-keywords.tool.ts │ │ │ │ │ │ ├── google-relevant-pages.ts │ │ │ │ │ │ ├── google-serp-competitors.tool.ts │ │ │ │ │ │ └── google-subdomains.ts │ │ │ │ │ ├── keyword-research │ │ │ │ │ │ ├── google-bulk-keyword-difficulty.tool.ts │ │ │ │ │ │ ├── google-historical-keyword-data.tool.ts │ │ │ │ │ │ ├── google-keyword-overview.tool.ts │ │ │ │ │ │ ├── google-keywords-for-site.tool.ts │ │ │ │ │ │ ├── google-keywords-ideas.tool.ts │ │ │ │ │ │ ├── google-keywords-suggestions.tool.ts │ │ │ │ │ │ ├── google-related-keywords.tool.ts │ │ │ │ │ │ └── google-search-intent.tool.ts │ │ │ │ │ └── market-analysis │ │ │ │ │ └── google-top-searches.tool.ts │ │ │ │ └── labs-filters.tool.ts │ │ │ ├── domain-analytics │ │ │ │ ├── domain-analytics-api.module.ts │ │ │ │ └── tools │ │ │ │ ├── technologies │ │ │ │ │ ├── domain-technologies-filters.tool.ts │ │ │ │ │ └── domain-technologies.tool.ts │ │ │ │ └── whois │ │ │ │ ├── whois-filters.tool.ts │ │ │ │ └── whois-overview.tool.ts │ │ │ ├── keywords-data │ │ │ │ ├── keywords-data-api.module.ts │ │ │ │ └── tools │ │ │ │ ├── dataforseo-trends │ │ │ │ │ ├── dataforseo-trends-demography.tool.ts │ │ │ │ │ ├── dataforseo-trends-explore.tool.ts │ │ │ │ │ └── dataforseo-trends-subregion-interests.tool.ts │ │ │ │ ├── google-ads │ │ │ │ │ └── google-ads-search-volume.tool.ts │ │ │ │ └── google-trends │ │ │ │ ├── google-trends-categories.tool.ts │ │ │ │ └── google-trends-explore.tool.ts │ │ │ ├── onpage │ │ │ │ ├── onpage-api.module.ts │ │ │ │ ├── onpage.prompt.ts │ │ │ │ └── tools │ │ │ │ ├── content-parsing.tool.ts │ │ │ │ ├── instant-pages.tool.ts │ │ │ │ └── lighthouse.tool.ts │ │ │ ├── prompt-definition.ts │ │ │ └── serp │ │ │ ├── serp-api.module.ts │ │ │ ├── serp.prompt.ts │ │ │ └── tools │ │ │ ├── serp-organic-live-advanced.tool.ts │ │ │ ├── serp-organic-locations-list.tool.ts │ │ │ ├── serp-youtube-locations-list.tool.ts │ │ │ ├── serp-youtube-organic-live-advanced.tool.ts │ │ │ ├── serp-youtube-video-comments-live-advanced-tool.ts │ │ │ ├── serp-youtube-video-info-live-advanced.tool.ts │ │ │ └── serp-youtube-video-subtitles-live-advanced-tool.ts │ │ └── utils │ │ ├── field-filter.ts │ │ ├── map-array-to-numbered-keys.ts │ │ ├── module-loader.ts │ │ └── version.ts │ ├── main │ │ ├── cli.ts │ │ ├── index-http.ts │ │ ├── index-sse-http.ts │ │ ├── index.ts │ │ ├── init-mcp-server.ts │ │ └── test.ts │ └── worker │ ├── index-worker.ts │ └── worker-configuration.d.ts ├── tsconfig.json ├── tsconfig.worker.json └── wrangler.jsonc ``` # Files -------------------------------------------------------------------------------- /src/core/modules/backlinks/tools/backlinks-competitors.tool.ts: -------------------------------------------------------------------------------- ```typescript import { z } from 'zod'; import { DataForSEOClient } from '../../../client/dataforseo.client.js'; import { BaseTool } from '../../base.tool.js'; export class BacklinksCompetitorsTool extends BaseTool { constructor(private client: DataForSEOClient) { super(client); } getName(): string { return 'backlinks_competitors'; } getDescription(): string { return "This endpoint will provide you with a list of competitors that share some part of the backlink profile with a target website, along with a number of backlink intersections and the rank of every competing website"; } getParams(): z.ZodRawShape { return { target: z.string().describe(`domain, subdomain or webpage to get backlinks for required field a domain or a subdomain should be specified without https:// and www. a page should be specified with absolute URL (including http:// or https://)`), limit: z.number().min(1).max(1000).default(10).optional().describe("the maximum number of returned domains"), offset: z.number().min(0).optional().describe( `offset in the results array of returned networks optional field default value: 0 if you specify the 10 value, the first ten domains in the results array will be omitted and the data will be provided for the successive pages` ), filters: this.getFilterExpression().optional().describe( `array of results filtering parameters optional field you can add several filters at once (8 filters maximum) you should set a logical operator and, or between the conditions the following operators are supported: regex, not_regex, =, <>, in, not_in, like, not_like, ilike, not_ilike, match, not_match you can use the % operator with like and not_like to match any string of zero or more characters example: ["rank",">","100"] [["target","like","%forbes%"], "and", [["rank",">","100"],"or",["intersections",">","5"]]]` ), order_by: z.array(z.string()).optional().describe( `results sorting rules optional field you can use the same values as in the filters array to sort the results possible sorting types: asc – results will be sorted in the ascending order desc – results will be sorted in the descending order you should use a comma to set up a sorting type example: ["rank,desc"] note that you can set no more than three sorting rules in a single request you should use a comma to separate several sorting rules example: ["intersections,desc","rank,asc"]` ), main_domain: z.boolean().optional().describe(`indicates if only main domain of the target will be included in the search if set to true, only the main domain will be included in search`).default(true), exclude_large_domains: z.boolean().optional().describe(`indicates whether large domain will appear in results if set to true, the results from the large domain (google.com, amazon.com, etc.) will be omitted`).default(true), exclude_internal_backlinks: z.boolean().optional().describe(`indicates if internal backlinks from subdomains to the target will be excluded from the results if set to true, the results will not include data on internal backlinks from subdomains of the same domain as target if set to false, internal links will be included in the results`).default(true) }; } async handle(params: any): Promise<any> { try { const response = await this.client.makeRequest('/v3/backlinks/competitors/live', 'POST', [{ target: params.target, limit: params.limit, offset: params.offset, filters: this.formatFilters(params.filters), order_by: this.formatOrderBy(params.order_by), main_domain: params.main_domain, exclude_large_domains: params.exclude_large_domains, exclude_internal_backlinks: params.exclude_internal_backlinks }]); return this.validateAndFormatResponse(response); } catch (error) { return this.formatErrorResponse(error); } } } ``` -------------------------------------------------------------------------------- /src/core/modules/keywords-data/tools/google-trends/google-trends-explore.tool.ts: -------------------------------------------------------------------------------- ```typescript import { z } from 'zod'; import { BaseTool } from '../../../base.tool.js'; import { DataForSEOClient } from '../../../../client/dataforseo.client.js'; export class GoogleTrendsExploreTool extends BaseTool { constructor(dataForSEOClient: DataForSEOClient) { super(dataForSEOClient); } getName(): string { return 'keywords_data_google_trends_explore'; } getDescription(): string { return 'This endpoint will provide you with the keyword popularity data from the ‘Explore’ feature of Google Trends. You can check keyword trends for Google Search, Google News, Google Images, Google Shopping, and YouTube'; } getParams(): z.ZodRawShape { return { location_name: z.string().nullable().default(null).describe(`full name of the location optional field in format "Country" example: United Kingdom`), language_code: z.string().nullable().default(null).describe(`Language two-letter ISO code (e.g., 'en'). optional field`), keywords: z.array(z.string()).describe(`keywords the maximum number of keywords you can specify: 5 the maximum number of characters you can specify in a keyword: 100 the minimum number of characters must be greater than 1 comma characters (,) in the specified keywords will be unset and ignored Note: keywords cannot consist of a combination of the following characters: < > | \ " - + = ~ ! : * ( ) [ ] { } Note: to obtain google_trends_topics_list and google_trends_queries_list items, specify no more than 1 keyword`), type: z.enum(['web', 'news', 'youtube','images','froogle']).default('web').describe(`google trends type`), date_from: z.string().optional().describe(`starting date of the time range if you don’t specify this field, the current day and month of the preceding year will be used by default minimal value for the web type: 2004-01-01 minimal value for other types: 2008-01-01 date format: "yyyy-mm-dd" example: "2019-01-15"`), date_to: z.string().optional() .describe( `ending date of the time range if you don’t specify this field, the today’s date will be used by default date format: "yyyy-mm-dd" example: "2019-01-15"`), time_range: z.enum(['past_hour', 'past_4_hours', 'past_day', 'past_7_days', 'past_30_days', 'past_90_days', 'past_12_months', 'past_5_years']) .default('past_7_days') .describe( `preset time ranges if you specify date_from or date_to parameters, this field will be ignored when setting a task`), item_types: z.array(z.enum(['google_trends_graph', 'google_trends_map', 'google_trends_topics_list', 'google_trends_queries_list'])) .default(['google_trends_graph']) .describe( `types of items returned to speed up the execution of the request, specify one item at a time`), category_code: z.nullable(z.number()).default(null) .describe( `google trends search category you can receive the list of available categories with their category_code by making a separate request to the keywords_data_google_trends_categories tool`) }; } async handle(params: any): Promise<any> { try { const response = await this.dataForSEOClient.makeRequest('/v3/keywords_data/google_trends/explore/live', 'POST', [{ location_name: params.location_name, language_code: params.language_code, keywords: params.keywords, type: params.type, date_from: params.date_from, date_to: params.date_to, time_range: params.time_range, item_types: params.item_types, category_code: params.category_code }]); return this.validateAndFormatResponse(response); } catch (error) { return this.formatErrorResponse(error); } } } ``` -------------------------------------------------------------------------------- /src/core/modules/onpage/onpage.prompt.ts: -------------------------------------------------------------------------------- ```typescript import { z } from 'zod'; import { PromptDefinition } from '../prompt-definition.js'; export const onpagePrompts: PromptDefinition[] = [ { name: 'identify_technical_performance_issues_affecting_crawlability_and_ranking', title: 'Identify technical performance issues affecting crawlability and ranking', params: { url: z.string().describe('The URL of the page to analyze'), }, handler: async (params) => { return { messages: [ { role: 'user', content: { type: 'text', text: `Audit page '${params.url}' for crawlability issues, including robots.txt restrictions, noindex tags, and broken internal links. Highlight what's preventing Google from indexing or ranking this page.` } } ] }; } }, { name: 'detect_missing_or_duplicate_meta_tags_hurting_seo', title: 'Detect missing or duplicate meta tags hurting SEO', params: { url: z.string().describe('The URL of the page to analyze'), }, handler: async (params) => { return { messages: [ { role: 'user', content: { type: 'text', text: `Review page '${params.url}' to check for missing or duplicate meta title and meta description tags, use validate_micromarkup, enable_javascript, and enable_browser_rendering. Let me know if the tags are too long, too short, or missing, and how to fix them.` } } ] }; } }, { name: 'check_for_slow_load_time_and_mobile_compatibility_issues', title: 'Check for slow load time and mobile compatibility issues', params: { url: z.string().describe('The URL of the page to analyze'), }, handler: async (params) => { return { messages: [ { role: 'user', content: { type: 'text', text: `Analyze page '${params.url}' for speed and mobile usability. Use load_resources, enable_javascript, and enable_browser_rendering. Tell me what's slowing it down or making it hard to use on mobile, include the measurements, and give practical steps to improve performance.` } } ] }; } }, { name: 'evaluate_internal_linking_and_crawl_depth_for_better_indexing', title: 'Evaluate internal linking and crawl depth for better indexing', params: { url: z.string().describe('The URL of the page to analyze'), }, handler: async (params) => { return { messages: [ { role: 'user', content: { type: 'text', text: `Check how well '${params.url}' is connected internally. Use load_resources, enable_javascript, and enable_browser_rendering. Tell me if it's buried too deep in the site structure or lacks internal links that could help search engines find and rank it. Include the data for each issue or metric.` } } ] }; } }, { name: 'analyze_keyword_optimization_and_content_gaps', title: 'Analyze keyword optimization and content gaps', params: { url: z.string().describe('The URL of the page to analyze'), keyword: z.string().describe('The primary keyword to optimize for'), }, handler: async (params) => { return { messages: [ { role: 'user', content: { type: 'text', text: `Evaluate '${params.url}' for how well it's optimized for the keyword '${params.keyword}'. Analyze on-page SEO elements like title, meta description, headings (H1-H6), internal links, and keyword usage, extract and parse all content elements (headings, paragraphs, alt attributes, etc.), and check for keyword placement and semantic relevance. Identify missing keyword placements and content gaps that could affect its relevance and ranking.` } } ] }; } }, ] ``` -------------------------------------------------------------------------------- /src/core/modules/dataforseo-labs/tools/google/competitor-research/google-serp-competitors.tool.ts: -------------------------------------------------------------------------------- ```typescript import { z } from 'zod'; import { DataForSEOClient } from '../../../../../client/dataforseo.client.js'; import { BaseTool } from '../../../../base.tool.js'; export class GoogleSERPCompetitorsTool extends BaseTool { constructor(private client: DataForSEOClient) { super(client); } getName(): string { return 'dataforseo_labs_google_serp_competitors'; } getDescription(): string { return "This endpoint will provide you with a list of domains ranking for the keywords you specify. You will also get SERP rankings, rating, estimated traffic volume, and visibility values the provided domains gain from the specified keywords."; } getParams(): z.ZodRawShape { return { keywords: z.array(z.string()).describe(`keywords array required field the results will be based on the keywords you specify in this array UTF-8 encoding; the keywords will be converted to lowercase format; you can specify the maximum of 200 keywords`), location_name: z.string().default("United States").describe(`full name of the location required field only in format "Country" (not "City" or "Region") example: 'United Kingdom', 'United States', 'Canada'`), language_code: z.string().default("en").describe( `language code required field example: en`), limit: z.number().min(1).max(1000).default(10).optional().describe("Maximum number of keywords to return"), offset: z.number().min(0).optional().describe( `offset in the results array of returned keywords optional field default value: 0 if you specify the 10 value, the first ten keywords in the results array will be omitted and the data will be provided for the successive keywords` ), filters: this.getFilterExpression().optional().describe( `you can add several filters at once (8 filters maximum) you should set a logical operator and, or between the conditions the following operators are supported: regex, not_regex, <, <=, >, >=, =, <>, in, not_in, match, not_match, ilike, not_ilike, like, not_like you can use the % operator with like and not_like, as well as ilike and not_ilike to match any string of zero or more characters merge operator must be a string and connect two other arrays, availible values: or, and. example: ["ranked_serp_element.serp_item.rank_group","<=",10] [["ranked_serp_element.serp_item.rank_group","<=",10],"or",["ranked_serp_element.serp_item.type","<>","paid"]] [["keyword_data.keyword_info.search_volume","<>",0],"and",[["ranked_serp_element.serp_item.type","<>","paid"],"or",["ranked_serp_element.serp_item.is_malicious","=",false]]]` ), order_by: z.array(z.string()).optional().describe( `results sorting rules optional field you can use the same values as in the filters array to sort the results possible sorting types: asc – results will be sorted in the ascending order desc – results will be sorted in the descending order the comma is used as a separator example: ["avg_position,asc"] default rule: ["rating,desc"] note that you can set no more than three sorting rules in a single request you should use a comma to separate several sorting rules example: ["avg_position,asc","etv,desc"]` ), include_subdomains: z.boolean().optional().describe("Include keywords from subdomains") }; } async handle(params: any): Promise<any> { try { const response = await this.client.makeRequest('/v3/dataforseo_labs/google/serp_competitors/live', 'POST', [{ keywords: params.keywords, location_name: params.location_name, language_code: params.language_code, limit: params.limit, offset: params.offset, filters: this.formatFilters(params.filters), order_by: this.formatOrderBy(params.order_by), }]); return this.validateAndFormatResponse(response); } catch (error) { return this.formatErrorResponse(error); } } } ``` -------------------------------------------------------------------------------- /src/core/modules/backlinks/backlinks-api.module.ts: -------------------------------------------------------------------------------- ```typescript import { DataForSEOClient } from '../../client/dataforseo.client.js'; import { BaseModule, ToolDefinition } from '../base.module.js'; import { PromptDefinition } from '../prompt-definition.js'; import { backlinksPrompts } from './backlinks.prompt.js'; import { BacklinksAnchorTool } from './tools/backlinks-anchor.tool.js'; import { BacklinksTool } from './tools/backlinks-backlinks.tool.js'; import { BacklinksBulkBacklinksTool } from './tools/backlinks-bulk-backlinks.tool.js'; import { BacklinksBulkNewLostBacklinksTool } from './tools/backlinks-bulk-new-lost-backlinks.tool.js'; import { BacklinksBulkNewLostReferringDomainsTool } from './tools/backlinks-bulk-new-lost-referring-domains.tool.js'; import { BacklinksBulkPagesSummaryTool } from './tools/backlinks-bulk-pages-summary.js'; import { BacklinksBulkRanksTool } from './tools/backlinks-bulk-ranks.tool.js'; import { BacklinksBulkReferringDomainsTool } from './tools/backlinks-bulk-referring-domains.tool.js'; import { BacklinksBulkSpamScoreTool } from './tools/backlinks-bulk-spam-score.tool.js'; import { BacklinksCompetitorsTool } from './tools/backlinks-competitors.tool.js'; import { BacklinksDomainIntersectionTool } from './tools/backlinks-domain-intersection.tool.js'; import { BacklinksDomainPagesSummaryTool } from './tools/backlinks-domain-pages-summary.tool.js'; import { BacklinksDomainPagesTool } from './tools/backlinks-domain-pages.tool.js'; import { BacklinksFiltersTool } from './tools/backlinks-filters.tool.js'; import { BacklinksPageIntersectionTool } from './tools/backlinks-page-intersection.tool.js'; import { BacklinksReferringDomainsTool } from './tools/backlinks-referring-domains.tool.js'; import { BacklinksReferringNetworksTool } from './tools/backlinks-referring-networks.tool.js'; import { BacklinksSummaryTool } from './tools/backlinks-summary.tool.js'; import { BacklinksTimeseriesNewLostSummaryTool } from './tools/backlinks-timeseries-new-lost-summary.tool.js'; import { BacklinksTimeseriesSummaryTool } from './tools/backlinks-timeseries-summary.tool.js'; export class BacklinksApiModule extends BaseModule { constructor(client: DataForSEOClient) { super(client); } getTools(): Record<string, ToolDefinition> { const tools = [ new BacklinksTool(this.dataForSEOClient), new BacklinksAnchorTool(this.dataForSEOClient), new BacklinksBulkBacklinksTool(this.dataForSEOClient), new BacklinksBulkNewLostReferringDomainsTool(this.dataForSEOClient), new BacklinksBulkNewLostBacklinksTool(this.dataForSEOClient), new BacklinksBulkRanksTool(this.dataForSEOClient), new BacklinksBulkReferringDomainsTool(this.dataForSEOClient), new BacklinksBulkSpamScoreTool(this.dataForSEOClient), new BacklinksCompetitorsTool(this.dataForSEOClient), new BacklinksDomainIntersectionTool(this.dataForSEOClient), new BacklinksDomainPagesSummaryTool(this.dataForSEOClient), new BacklinksDomainPagesTool(this.dataForSEOClient), new BacklinksPageIntersectionTool(this.dataForSEOClient), new BacklinksReferringDomainsTool(this.dataForSEOClient), new BacklinksReferringNetworksTool(this.dataForSEOClient), new BacklinksSummaryTool(this.dataForSEOClient), new BacklinksTimeseriesNewLostSummaryTool(this.dataForSEOClient), new BacklinksTimeseriesSummaryTool(this.dataForSEOClient), new BacklinksBulkPagesSummaryTool(this.dataForSEOClient), new BacklinksFiltersTool(this.dataForSEOClient) // Add more tools here ]; return tools.reduce((acc, tool) => ({ ...acc, [tool.getName()]: { description: tool.getDescription(), params: tool.getParams(), handler: (params: any) => tool.handle(params), }, }), {}); } getPrompts(): Record<string, PromptDefinition> { return backlinksPrompts.reduce((acc, prompt) => ({ ...acc, [prompt.name]: { description: prompt.description, params: prompt.params, handler: (params: any) => { return prompt.handler(params); }, }, }), {}); } } ``` -------------------------------------------------------------------------------- /src/core/modules/business-data-api/tools/listings/business-listings-search.tool.ts: -------------------------------------------------------------------------------- ```typescript import { z } from 'zod'; import { DataForSEOClient } from '../../../../client/dataforseo.client.js'; import { BaseTool, DataForSEOResponse } from '../../../base.tool.js'; export class BusinessDataBusinessListingsSearchTool extends BaseTool { constructor(private client: DataForSEOClient) { super(client); } getName(): string { return 'business_data_business_listings_search'; } getDescription(): string { return `Business Listings Search API provides results containing information about business entities listed on Google Maps in the specified categories. You will receive the address, contacts, rating, working hours, and other relevant data`; } getParams(): z.ZodRawShape { return { description: z.string().optional().describe(`description of the element in SERP optional field the description of the business entity for which the results are collected; can contain up to 200 characters`), title: z.string().optional().describe(`title of the element in SERP optional field the name of the business entity for which the results are collected; can contain up to 200 characters`), categories: z.array(z.string()).optional().describe(`business categories the categories you specify are used to search for business listings; if you don’t use this field, we will return business listings found in the specified location; you can specify up to 10 categories`), location_coordinate: z.string().optional().describe(`GPS coordinates of a location optional field location_coordinate parameter should be specified in the “latitude,longitude,radius” format the maximum number of decimal digits for “latitude” and “longitude”: 7 the value of “radius” is specified in kilometres (km) the minimum value for “radius”: 1 the maximum value for “radius”: 100000 example: 53.476225,-2.243572,200`), limit: z.number().min(1).max(1000).default(10).optional().describe("the maximum number of returned businesses"), offset: z.number().min(0).optional().describe( `offset in the results array of returned businesses optional field default value: 0 if you specify the 10 value, the first ten entities in the results array will be omitted and the data will be provided for the successive entities` ), filters: this.getFilterExpression().optional().describe( `array of results filtering parameters optional field you can add several filters at once (8 filters maximum) you should set a logical operator and, or between the conditions the following operators are supported: regex, not_regex, <, <=, >, >=, =, <>, in, not_in, like, not_like, match, not_match you can use the % operator with like and not_like to match any string of zero or more characters example: ["rating.value",">",3]` ), order_by: z.array(z.string()).optional().describe( `results sorting rules optional field you can use the same values as in the filters array to sort the results possible sorting types: asc – results will be sorted in the ascending order desc – results will be sorted in the descending order you should use a comma to set up a sorting parameter example: ["rating.value,desc"]note that you can set no more than three sorting rules in a single request you should use a comma to separate several sorting rules example: ["rating.value,desc","rating.votes_count,desc"]` ), is_claimed: z.boolean().optional().describe(`indicates whether the business is verified by its owner on Google Maps`).default(true) }; } async handle(params: any): Promise<any> { try { const response = await this.client.makeRequest('/v3/business_data/business_listings/search/live', 'POST', [{ description: params.description, title: params.title, categories: params.categories, limit: params.limit, offset: params.offset, filters: this.formatFilters(params.filters), order_by: this.formatOrderBy(params.order_by), location_coordinate: params.location_coordinate }]); return this.validateAndFormatResponse(response); } catch (error) { return this.formatErrorResponse(error); } } } ``` -------------------------------------------------------------------------------- /src/core/modules/dataforseo-labs/tools/google/competitor-research/google-domain-competitors.tool.ts: -------------------------------------------------------------------------------- ```typescript import { z } from 'zod'; import { DataForSEOClient } from '../../../../../client/dataforseo.client.js'; import { BaseTool } from '../../../../base.tool.js'; export class GoogleDomainCompetitorsTool extends BaseTool { constructor(private client: DataForSEOClient) { super(client); } getName(): string { return 'dataforseo_labs_google_competitors_domain'; } getDescription(): string { return `This endpoint will provide you with a full overview of ranking and traffic data of the competitor domains from organic and paid search. In addition to that, you will get the metrics specific to the keywords both competitor domains and your domain rank for within the same SERP.`; } getParams(): z.ZodRawShape { return { target: z.string().describe(`target domain`), location_name: z.string().default("United States").describe(`full name of the location required field only in format "Country" (not "City" or "Region") example: 'United Kingdom', 'United States', 'Canada'`), language_code: z.string().default("en").describe( `language code required field example: en`), ignore_synonyms: z.boolean().default(true).describe( `ignore highly similar keywords, if set to true, results will be more accurate`), limit: z.number().min(1).max(1000).default(10).optional().describe("Maximum number of keywords to return"), offset: z.number().min(0).optional().describe( `offset in the results array of returned keywords optional field default value: 0 if you specify the 10 value, the first ten keywords in the results array will be omitted and the data will be provided for the successive keywords` ), filters: this.getFilterExpression().optional().describe( `you can add several filters at once (8 filters maximum) you should set a logical operator and, or between the conditions the following operators are supported: regex, not_regex, <, <=, >, >=, =, <>, in, not_in, match, not_match, ilike, not_ilike, like, not_like you can use the % operator with like and not_like, as well as ilike and not_ilike to match any string of zero or more characters merge operator must be a string and connect two other arrays, availible values: or, and. example: ["metrics.organic.count",">",50] [["metrics.organic.pos_1","<>",0],"and",["metrics.organic.impressions_etv",">=","10"]] [[["metrics.organic.count",">=",50],"and",["metrics.organic.pos_1","in",[1,5]]], "or", ["metrics.organic.etv",">=","100"]]` ), order_by: z.array(z.string()).optional().describe( `results sorting rules optional field you can use the same values as in the filters array to sort the results possible sorting types: asc – results will be sorted in the ascending order desc – results will be sorted in the descending order you should use a comma to set up a sorting parameter default rule: ["relevance,desc"] example: ["relevance,desc","keyword_info.search_volume,desc"]` ), exclude_top_domains: z.boolean().default(true).describe(`indicates whether to exclude world's largest websites optional field default value: false set to true if you want to get highly-relevant competitors excluding the top websites`), include_clickstream_data: z.boolean().optional().default(false).describe( `Include or exclude data from clickstream-based metrics in the result`) }; } async handle(params: any): Promise<any> { try { const response = await this.client.makeRequest('/v3/dataforseo_labs/google/competitors_domain/live', 'POST', [{ target: params.target, location_name: params.location_name, language_code: params.language_code, ignore_synonyms: params.ignore_synonyms, filters: this.formatFilters(params.filters), order_by: this.formatOrderBy(params.order_by), exclude_top_domains: params.exclude_top_domains, item_types: ['organic'], include_clickstream_data: params.include_clickstream_data }]); return this.validateAndFormatResponse(response); } catch (error) { return this.formatErrorResponse(error); } } } ``` -------------------------------------------------------------------------------- /src/core/modules/content-analysis/tools/content-analysis-summary.ts: -------------------------------------------------------------------------------- ```typescript import { any, z } from 'zod'; import { BaseTool } from '../../base.tool.js'; import { DataForSEOClient } from '../../../client/dataforseo.client.js'; export class ContentAnalysisSummaryTool extends BaseTool { constructor(dataForSEOClient: DataForSEOClient) { super(dataForSEOClient); } getName(): string { return 'content_analysis_summary'; } getDescription(): string { return `This endpoint will provide you with an overview of citation data available for the target keyword`; } getParams(): z.ZodRawShape { return { keyword: z.string().describe(`target keyword Note: to match an exact phrase instead of a stand-alone keyword, use double quotes and backslashes;`), keyword_fields: z.object({ title: z.string().optional(), main_title: z.string().optional(), previous_title: z.string().optional(), snippet: z.string().optional() }).optional().describe( `target keyword fields and target keywords use this parameter to filter the dataset by keywords that certain fields should contain; you can indicate several fields; Note: to match an exact phrase instead of a stand-alone keyword, use double quotes and backslashes; example: { "snippet": "\\"logitech mouse\\"", "main_title": "sale" }` ), page_type: z.array(z.enum(['ecommerce','news','blogs', 'message-boards','organization'])).optional().describe(`target page types`), initial_dataset_filters: this.getFilterExpression().optional().describe( `initial dataset filtering parameters initial filtering parameters that apply to fields in the Search endpoint; you can add several filters at once (8 filters maximum); you should set a logical operator and, or between the conditions; the following operators are supported: regex, not_regex, <, <=, >, >=, =, <>, in, not_in, like,not_like, has, has_not, match, not_match you can use the % operator with like and not_like to match any string of zero or more characters; example: ["domain","<>", "logitech.com"] [["domain","<>","logitech.com"],"and",["content_info.connotation_types.negative",">",1000]] [["domain","<>","logitech.com"]], "and", [["content_info.connotation_types.negative",">",1000], "or", ["content_info.text_category","has",10994]]` ), positive_connotation_threshold: z.number() .describe(`positive connotation threshold specified as the probability index threshold for positive sentiment related to the citation content if you specify this field, connotation_types object in the response will only contain data on citations with positive sentiment probability more than or equal to the specified value`).min(0).max(1).optional().default(0.4), sentiments_connotation_threshold: z.number() .describe(`sentiment connotation threshold specified as the probability index threshold for sentiment connotations related to the citation content if you specify this field, sentiment_connotations object in the response will only contain data on citations where the probability per each sentiment is more than or equal to the specified value`) .min(0).max(1).optional().default(0.4), internal_list_limit: z.number().min(1).max(20).default(1) .describe( `maximum number of elements within internal arrays you can use this field to limit the number of elements within the following arrays`) .optional(), }; } async handle(params: any): Promise<any> { try { const response = await this.dataForSEOClient.makeRequest('/v3/content_analysis/summary/live', 'POST', [{ keyword: params.keyword, keyword_fields: params.keyword_fields, page_type: params.page_type, initial_dataset_filters: this.formatFilters(params.initial_dataset_filters), positive_connotation_threshold: params.positive_connotation_threshold, sentiments_connotation_threshold: params.sentiments_connotation_threshold, internal_list_limit: params.internal_list_limit }]); return this.validateAndFormatResponse(response); } catch (error) { return this.formatErrorResponse(error); } } } ``` -------------------------------------------------------------------------------- /src/core/modules/dataforseo-labs/tools/google/competitor-research/google-subdomains.ts: -------------------------------------------------------------------------------- ```typescript import { z } from 'zod'; import { DataForSEOClient } from '../../../../../client/dataforseo.client.js'; import { BaseTool } from '../../../../base.tool.js'; export class GoogleSubdomainsTool extends BaseTool { constructor(private client: DataForSEOClient) { super(client); } getName(): string { return 'dataforseo_labs_google_subdomains'; } getDescription(): string { return `This endpoint will provide you with a list of subdomains of the specified domain, along with the ranking distribution across organic and paid search. In addition to that, you will also get the estimated traffic volume of subdomains based on search volume.`; } getParams(): z.ZodRawShape { return { target: z.string().describe(`target domain`), location_name: z.string().default("United States").describe(`full name of the location required field only in format "Country" (not "City" or "Region") example: 'United Kingdom', 'United States', 'Canada'`), language_code: z.string().default("en").describe( `language code required field example: en`), ignore_synonyms: z.boolean().default(true).describe( `ignore highly similar keywords, if set to true, results will be more accurate`), limit: z.number().min(1).max(1000).default(10).optional().describe("Maximum number of keywords to return"), offset: z.number().min(0).optional().describe( `offset in the results array of returned keywords optional field default value: 0 if you specify the 10 value, the first ten keywords in the results array will be omitted and the data will be provided for the successive keywords` ), filters: this.getFilterExpression().optional().describe( `you can add several filters at once (8 filters maximum) you should set a logical operator and, or between the conditions the following operators are supported: regex, not_regex, <, <=, >, >=, =, <>, in, not_in, match, not_match, ilike, not_ilike, like, not_like you can use the % operator with like and not_like, as well as ilike and not_ilike to match any string of zero or more characters merge operator must be a string and connect two other arrays, availible values: or, and. example: ["metrics.organic.count",">",50] [["metrics.organic.pos_1","<>",0],"and",["metrics.organic.impressions_etv",">=","10"]] [[["metrics.organic.count",">=",50],"and",["metrics.organic.pos_1","in",[1,5]]],"or",["metrics.organic.etv",">=","100"]]` ), order_by: z.array(z.string()).optional().describe( `results sorting rules optional field you can use the same values as in the filters array to sort the results possible sorting types: asc – results will be sorted in the ascending order desc – results will be sorted in the descending order you should use a comma to specify a sorting type example: ["metrics.paid.etv,asc"] Note: you can set no more than three sorting rules in a single request you should use a comma to separate several sorting rules example: ["metrics.organic.etv,desc","metrics.paid.count,asc"] default rule: ["metrics.organic.count,desc"]` ), item_types: z.array(z.string()).optional().describe( `item types to return optional field default: ['organic'] possible values: organic paid` ), include_clickstream_data: z.boolean().optional().default(false).describe( `Include or exclude data from clickstream-based metrics in the result`) }; } async handle(params: any): Promise<any> { try { const response = await this.client.makeRequest('/v3/dataforseo_labs/google/subdomains/live', 'POST', [{ target: params.target, location_name: params.location_name, language_code: params.language_code, ignore_synonyms: params.ignore_synonyms, filters: this.formatFilters(params.filters), order_by: this.formatOrderBy(params.order_by), exclude_top_domains: params.exclude_top_domains, item_types: params.item_types, include_clickstream_data: params.include_clickstream_data, limit: params.limit, offset: params.offset }]); return this.validateAndFormatResponse(response); } catch (error) { return this.formatErrorResponse(error); } } } ``` -------------------------------------------------------------------------------- /src/core/modules/dataforseo-labs/tools/google/competitor-research/google-ranked-keywords.tool.ts: -------------------------------------------------------------------------------- ```typescript import { z } from 'zod'; import { DataForSEOClient } from '../../../../../client/dataforseo.client.js'; import { BaseTool } from '../../../../base.tool.js'; export class GoogleRankedKeywordsTool extends BaseTool { constructor(private client: DataForSEOClient) { super(client); } getName(): string { return 'dataforseo_labs_google_ranked_keywords'; } getDescription(): string { return "This endpoint will provide you with the list of keywords that any domain or webpage is ranking for. You will also get SERP elements related to the keyword position, as well as impressions, monthly searches and other data relevant to the returned keywords."; } getParams(): z.ZodRawShape { return { target: z.string().describe(`domain name or page url required field the domain name of the target website or URL of the target webpage; the domain name must be specified without https:// or www.; the webpage URL must be specified with https:// or www. Note: if you specify the webpage URL without https:// or www., the result will be returned for the entire domain rather than the specific page `), location_name: z.string().default("United States").describe(`full name of the location required field only in format "Country" (not "City" or "Region") example: 'United Kingdom', 'United States', 'Canada'`), language_code: z.string().default("en").describe( `language code required field example: en`), limit: z.number().min(1).max(1000).default(10).optional().describe("Maximum number of keywords to return"), offset: z.number().min(0).optional().describe( `offset in the results array of returned keywords optional field default value: 0 if you specify the 10 value, the first ten keywords in the results array will be omitted and the data will be provided for the successive keywords` ), filters: this.getFilterExpression().optional().describe( `Array of filter conditions and logical operators. Each filter condition is an array of [field, operator, value]. Maximum 8 filters allowed. Available operators: =, <>, <, <=, >, >=, in, not_in, like, not_like, ilike, not_ilike, regex, not_regex, match, not_match Logical operators: "and", "or" Examples: Simple filter: [["ranked_serp_element.serp_item.rank_group","<=",10]] With logical operator: [["ranked_serp_element.serp_item.rank_group","<=",10],"or",["ranked_serp_element.serp_item.type","<>","paid"]] Complex filter: [["keyword_data.keyword_info.search_volume","<>",0],"and",[["ranked_serp_element.serp_item.type","<>","paid"],"or",["ranked_serp_element.serp_item.is_malicious","=",false]]]` ), order_by: z.array(z.string()).optional().describe( `results sorting rules optional field you can use the same values as in the filters array to sort the results possible sorting types: asc – results will be sorted in the ascending order desc – results will be sorted in the descending order you should use a comma to set up a sorting type example: ["keyword_data.keyword_info.competition,desc"] default rule: ["ranked_serp_element.serp_item.rank_group,asc"] note that you can set no more than three sorting rules in a single request you should use a comma to separate several sorting rules example: ["keyword_data.keyword_info.search_volume,desc","keyword_data.keyword_info.cpc,desc"]` ), include_subdomains: z.boolean().optional().describe("Include keywords from subdomains"), include_clickstream_data: z.boolean().optional().default(false).describe( `Include or exclude data from clickstream-based metrics in the result`) }; } async handle(params: any): Promise<any> { try { const response = await this.client.makeRequest('/v3/dataforseo_labs/google/ranked_keywords/live', 'POST', [{ target: params.target, location_name: params.location_name, language_code: params.language_code, limit: params.limit, offset: params.offset, filters: this.formatFilters(params.filters), order_by: this.formatOrderBy(params.order_by), include_subdomains: params.include_subdomains, include_clickstream_data: params.include_clickstream_data }]); return this.validateAndFormatResponse(response); } catch (error) { return this.formatErrorResponse(error); } } } ``` -------------------------------------------------------------------------------- /src/core/modules/dataforseo-labs/tools/google/keyword-research/google-keywords-suggestions.tool.ts: -------------------------------------------------------------------------------- ```typescript import { z } from 'zod'; import { DataForSEOClient } from '../../../../../client/dataforseo.client.js'; import { BaseTool } from '../../../../base.tool.js'; export class GoogleKeywordsSuggestionsTool extends BaseTool { constructor(private client: DataForSEOClient) { super(client); } getName(): string { return 'dataforseo_labs_google_keyword_suggestions'; } getDescription(): string { return `The Keyword Suggestions provides search queries that include the specified seed keyword. The algorithm is based on the full-text search for the specified keyword and therefore returns only those search terms that contain the keyword you set in the POST array with additional words before, after, or within the specified key phrase. Returned keyword suggestions can contain the words from the specified key phrase in a sequence different from the one you specify. As a result, you will get a list of long-tail keywords with each keyword in the list matching the specified search term. Along with each suggested keyword, you will get its search volume rate for the last month, search volume trend for the previous 12 months, as well as current cost-per-click and competition values. Moreover, this endpoint supplies minimum, maximum and average values of daily impressions, clicks and CPC for each result. `; } getParams(): z.ZodRawShape { return { keyword: z.string().describe(`target keyword`), location_name: z.string().default("United States").describe(`full name of the location required field only in format "Country" (not "City" or "Region") example: 'United Kingdom', 'United States', 'Canada'`), language_code: z.string().default("en").describe( `language code required field example: en`), limit: z.number().min(1).max(1000).default(10).optional().describe("Maximum number of keywords to return"), offset: z.number().min(0).optional().describe( `offset in the results array of returned keywords optional field default value: 0 if you specify the 10 value, the first ten keywords in the results array will be omitted and the data will be provided for the successive keywords` ), filters: this.getFilterExpression().optional().describe( `you can add several filters at once (8 filters maximum) you should set a logical operator and, or between the conditions the following operators are supported: regex, not_regex, <, <=, >, >=, =, <>, in, not_in, match, not_match, ilike, not_ilike, like, not_like you can use the % operator with like and not_like, as well as ilike and not_ilike to match any string of zero or more characters merge operator must be a string and connect two other arrays, availible values: or, and. example: ["keyword_info.search_volume",">",0] [["keyword_info.search_volume","in",[0,1000]], "and", ["keyword_info.competition_level","=","LOW"]][["keyword_info.search_volume",">",100], "and", [["keyword_info.cpc","<",0.5], "or", ["keyword_info.high_top_of_page_bid","<=",0.5]]]` ), order_by: z.array(z.string()).optional().describe( `results sorting rules optional field you can use the same values as in the filters array to sort the results possible sorting types: asc – results will be sorted in the ascending order desc – results will be sorted in the descending order a comma is used as a separator example: ["keyword_info.competition,desc"] default rule: ["keyword_info.search_volume,desc"] note that you can set no more than three sorting rules in a single request you should use a comma to separate several sorting rules example: ["keyword_info.search_volume,desc","keyword_info.cpc,desc"]` ), include_clickstream_data: z.boolean().optional().default(false).describe( `Include or exclude data from clickstream-based metrics in the result`) }; } async handle(params: any): Promise<any> { try { const response = await this.client.makeRequest('/v3/dataforseo_labs/google/keyword_suggestions/live', 'POST', [{ keyword: params.keyword, location_name: params.location_name, language_code: params.language_code, limit: params.limit, offset: params.offset, filters: this.formatFilters(params.filters), order_by: this.formatOrderBy(params.order_by), include_clickstream_data: params.include_clickstream_data }]); return this.validateAndFormatResponse(response); } catch (error) { return this.formatErrorResponse(error); } } } ``` -------------------------------------------------------------------------------- /src/core/modules/dataforseo-labs/tools/google/keyword-research/google-related-keywords.tool.ts: -------------------------------------------------------------------------------- ```typescript import { z } from 'zod'; import { DataForSEOClient } from '../../../../../client/dataforseo.client.js'; import { BaseTool } from '../../../../base.tool.js'; export class GoogleRelatedKeywordsTool extends BaseTool { constructor(private client: DataForSEOClient) { super(client); } getName(): string { return 'dataforseo_labs_google_related_keywords'; } getDescription(): string { return `The Related Keywords endpoint provides keywords appearing in the "searches related to" SERP element You can get up to 4680 keyword ideas by specifying the search depth. Each related keyword comes with the list of relevant product categories, search volume rate for the last month, search volume trend for the previous 12 months, as well as current cost-per-click and competition values. Moreover, this endpoint supplies minimum, maximum and average values of daily impressions, clicks and CPC for each result. Datasource: DataForSEO SERPs Database Search algorithm: depth-first search for queries appearing in the "search related to" element of SERP for the specified seed keyword. `; } getParams(): z.ZodRawShape { return { keyword: z.string().describe(`target keyword`), depth: z.number().min(0).max(4).default(1).describe(`keyword search depth`), location_name: z.string().default("United States").describe(`full name of the location required field only in format "Country" (not "City" or "Region") example: 'United Kingdom', 'United States', 'Canada'`), language_code: z.string().default("en").describe( `language code required field example: en`), limit: z.number().min(1).max(1000).default(10).optional().describe("Maximum number of keywords to return"), offset: z.number().min(0).optional().describe( `offset in the results array of returned keywords optional field default value: 0 if you specify the 10 value, the first ten keywords in the results array will be omitted and the data will be provided for the successive keywords` ), filters: this.getFilterExpression().optional().describe( `you can add several filters at once (8 filters maximum) you should set a logical operator and, or between the conditions the following operators are supported: regex, not_regex, <, <=, >, >=, =, <>, in, not_in, match, not_match, ilike, not_ilike, like, not_like you can use the % operator with like and not_like, as well as ilike and not_ilike to match any string of zero or more characters merge operator must be a string and connect two other arrays, availible values: or, and. example: ["keyword_data.keyword_info.search_volume",">",0] [["keyword_info.search_volume","in",[0,1000]], "and", ["keyword_data.keyword_info.competition_level","=","LOW"]] [["keyword_data.keyword_info.search_volume",">",100], "and", [["keyword_data.keyword_info.cpc","<",0.5], "or", ["keyword_data.keyword_info.high_top_of_page_bid","<=",0.5]]]` ), order_by: z.array(z.string()).optional().describe( `results sorting rules optional field you can use the same values as in the filters array to sort the results possible sorting types: asc – results will be sorted in the ascending order desc – results will be sorted in the descending order you should use a comma to set up a sorting type example: ["keyword_data.keyword_info.competition,desc"] default rule: ["keyword_data.keyword_info.search_volume,desc"] note that you can set no more than three sorting rules in a single request you should use a comma to separate several sorting rules example: ["keyword_data.keyword_info.search_volume,desc","keyword_data.keyword_info.cpc,desc"]` ), include_clickstream_data: z.boolean().optional().default(false).describe( `Include or exclude data from clickstream-based metrics in the result`) }; } async handle(params: any): Promise<any> { try { const response = await this.client.makeRequest('/v3/dataforseo_labs/google/related_keywords/live', 'POST', [{ keyword: params.keyword, location_name: params.location_name, language_code: params.language_code, depth: params.depth, limit: params.limit, offset: params.offset, filters: this.formatFilters(params.filters), order_by: this.formatOrderBy(params.order_by), include_clickstream_data: params.include_clickstream_data }]); return this.validateAndFormatResponse(response); } catch (error) { return this.formatErrorResponse(error); } } } ``` -------------------------------------------------------------------------------- /src/core/modules/dataforseo-labs/dataforseo-labs-api.module.ts: -------------------------------------------------------------------------------- ```typescript import { DataForSEOClient } from '../../client/dataforseo.client.js'; import { BaseModule, ToolDefinition } from '../base.module.js'; import { PromptDefinition } from '../prompt-definition.js'; import { GoogleDomainCompetitorsTool } from './tools/google/competitor-research/google-domain-competitors.tool.js'; import { GoogleDomainRankOverviewTool } from './tools/google/competitor-research/google-domain-rank-overview.tool.js'; import { GoogleKeywordsIdeasTool } from './tools/google/keyword-research/google-keywords-ideas.tool.js'; import { GoogleKeywordsSuggestionsTool } from './tools/google/keyword-research/google-keywords-suggestions.tool.js'; import { GoogleRankedKeywordsTool } from './tools/google/competitor-research/google-ranked-keywords.tool.js'; import { GoogleRelatedKeywordsTool } from './tools/google/keyword-research/google-related-keywords.tool.js'; import { GoogleBulkKeywordDifficultyTool } from './tools/google/keyword-research/google-bulk-keyword-difficulty.tool.js'; import { GoogleTopSearchesTool } from './tools/google/market-analysis/google-top-searches.tool.js'; import { GoogleKeywordOverviewTool } from './tools/google/keyword-research/google-keyword-overview.tool.js'; import { GoogleKeywordsForSiteTool } from './tools/google/keyword-research/google-keywords-for-site.tool.js'; import { GoogleSubdomainsTool } from './tools/google/competitor-research/google-subdomains.js'; import { GoogleSERPCompetitorsTool } from './tools/google/competitor-research/google-serp-competitors.tool.js'; import { GoogleHistoricalSERP } from './tools/google/competitor-research/google-historical-serp.js'; import { GoogleSearchIntentTool } from './tools/google/keyword-research/google-search-intent.tool.js'; import { GoogleDomainIntersectionsTool } from './tools/google/competitor-research/google-domain-intersection.tool.js'; import { GoogleHistoricalDomainRankOverviewTool } from './tools/google/competitor-research/google-historical-domain-rank-overview.tool.js'; import { GooglePageIntersectionsTool } from './tools/google/competitor-research/google-page-intersection.tool.js'; import { DataForSeoLabsFilterTool } from './tools/labs-filters.tool.js'; import { GoogleBulkTrafficEstimationTool } from './tools/google/competitor-research/google-bulk-traffic-estimation.tool.js'; import { GoogleHistoricalKeywordDataTool } from './tools/google/keyword-research/google-historical-keyword-data.tool.js'; import { GoogleRelevantPagesTool } from './tools/google/competitor-research/google-relevant-pages.js'; import { datalabsPrompts } from './dataforseo-labs.prompts.js'; export class DataForSEOLabsApi extends BaseModule { constructor(client: DataForSEOClient) { super(client); } getTools(): Record<string, ToolDefinition> { const tools = [ new GoogleRankedKeywordsTool(this.dataForSEOClient), new GoogleDomainCompetitorsTool(this.dataForSEOClient), new GoogleDomainRankOverviewTool(this.dataForSEOClient), new GoogleKeywordsIdeasTool(this.dataForSEOClient), new GoogleRelatedKeywordsTool(this.dataForSEOClient), new GoogleKeywordsSuggestionsTool(this.dataForSEOClient), new GoogleHistoricalSERP(this.dataForSEOClient), new GoogleSERPCompetitorsTool(this.dataForSEOClient), new GoogleBulkKeywordDifficultyTool(this.dataForSEOClient), new GoogleSubdomainsTool(this.dataForSEOClient), new GoogleKeywordOverviewTool(this.dataForSEOClient), new GoogleTopSearchesTool(this.dataForSEOClient), new GoogleSearchIntentTool(this.dataForSEOClient), new GoogleKeywordsForSiteTool(this.dataForSEOClient), new GoogleDomainIntersectionsTool(this.dataForSEOClient), new GoogleHistoricalDomainRankOverviewTool(this.dataForSEOClient), new GooglePageIntersectionsTool(this.dataForSEOClient), new GoogleBulkTrafficEstimationTool(this.dataForSEOClient), new DataForSeoLabsFilterTool(this.dataForSEOClient), new GoogleHistoricalKeywordDataTool(this.dataForSEOClient), new GoogleRelevantPagesTool(this.dataForSEOClient), // Add more tools here ]; return tools.reduce((acc, tool) => ({ ...acc, [tool.getName()]: { description: tool.getDescription(), params: tool.getParams(), handler: (params: any) => { return tool.handle(params); }, }, }), {}); } getPrompts(): Record<string, PromptDefinition> { return datalabsPrompts.reduce((acc, prompt) => ({ ...acc, [prompt.name]: { description: prompt.description, params: prompt.params, handler: (params: any) => { return prompt.handler(params); }, }, }), {}); } } ``` -------------------------------------------------------------------------------- /src/core/modules/dataforseo-labs/tools/google/keyword-research/google-keywords-ideas.tool.ts: -------------------------------------------------------------------------------- ```typescript import { z } from 'zod'; import { DataForSEOClient } from '../../../../../client/dataforseo.client.js'; import { BaseTool } from '../../../../base.tool.js'; export class GoogleKeywordsIdeasTool extends BaseTool { constructor(private client: DataForSEOClient) { super(client); } getName(): string { return 'dataforseo_labs_google_keyword_ideas'; } getDescription(): string { return `The Keyword Ideas provides search terms that are relevant to the product or service categories of the specified keywords. The algorithm selects the keywords which fall into the same categories as the seed keywords specified in a POST array. As a result, you will get a list of relevant keyword ideas for up to 200 seed keywords. Along with each keyword idea, you will get its search volume rate for the last month, search volume trend for the previous 12 months, as well as current cost-per-click and competition values. Moreover, this endpoint supplies minimum, maximum and average values of daily impressions, clicks and CPC for each result. `; } getParams(): z.ZodRawShape { return { keywords: z.array(z.string()).describe(`target keywords`), location_name: z.string().default("United States").describe(`full name of the location required field only in format "Country" (not "City" or "Region") example: 'United Kingdom', 'United States', 'Canada'`), language_code: z.string().default("en").describe( `language code required field example: en`), limit: z.number().min(1).max(1000).default(10).optional().describe("Maximum number of keywords to return"), offset: z.number().min(0).optional().describe( `offset in the results array of returned keywords optional field default value: 0 if you specify the 10 value, the first ten keywords in the results array will be omitted and the data will be provided for the successive keywords` ), filters: this.getFilterExpression().optional().describe( `you can add several filters at once (8 filters maximum) you should set a logical operator and, or between the conditions the following operators are supported: regex, not_regex, <, <=, >, >=, =, <>, in, not_in, match, not_match, ilike, not_ilike, like, not_like you can use the % operator with like and not_like, as well as ilike and not_ilike to match any string of zero or more characters merge operator must be a string and connect two other arrays, availible values: or, and. example: ["keyword_info.search_volume",">",0] [["keyword_info.search_volume","in",[0,1000]],"and",["keyword_info.competition_level","=","LOW"]] [["keyword_info.search_volume",">",100],"and",[["keyword_info.cpc","<",0.5],"or",["keyword_info.high_top_of_page_bid","<=",0.5]]]` ), order_by: z.array(z.string()).optional().describe( `results sorting rules optional field you can use the same values as in the filters array to sort the results possible sorting types: asc – results will be sorted in the ascending order desc – results will be sorted in the descending order you should use a comma to set up a sorting parameter default rule: ["relevance,desc"] relevance is used as the default sorting rule to provide you with the closest keyword ideas. We recommend using this sorting rule to get highly-relevant search terms. Note that relevance is only our internal system identifier, so it can not be used as a filter, and you will not find this field in the result array. The relevance score is based on a similar principle as used in the Keywords For Keywords endpoint. note that you can set no more than three sorting rules in a single request you should use a comma to separate several sorting rules example: ["relevance,desc","keyword_info.search_volume,desc"]` ), include_clickstream_data: z.boolean().optional().default(false).describe( `Include or exclude data from clickstream-based metrics in the result`) }; } async handle(params: any): Promise<any> { try { const response = await this.client.makeRequest('/v3/dataforseo_labs/google/keyword_ideas/live', 'POST', [{ keywords: params.keywords, location_name: params.location_name, language_code: params.language_code, limit: params.limit, offset: params.offset, filters: this.formatFilters(params.filters), order_by: this.formatOrderBy(params.order_by), include_clickstream_data: params.include_clickstream_data }]); return this.validateAndFormatResponse(response); } catch (error) { return this.formatErrorResponse(error); } } } ``` -------------------------------------------------------------------------------- /src/core/modules/dataforseo-labs/tools/google/competitor-research/google-relevant-pages.ts: -------------------------------------------------------------------------------- ```typescript import { z } from 'zod'; import { DataForSEOClient } from '../../../../../client/dataforseo.client.js'; import { BaseTool } from '../../../../base.tool.js'; export class GoogleRelevantPagesTool extends BaseTool { constructor(private client: DataForSEOClient) { super(client); } getName(): string { return 'dataforseo_labs_google_relevant_pages'; } getDescription(): string { return `This endpoint will provide you with rankings and traffic data for the web pages of the specified domain. You will be able to review each page’s ranking distribution and estimated monthly traffic volume from both organic and paid searches.`; } getParams(): z.ZodRawShape { return { target: z.string().describe(`target domain`), location_name: z.string().default("United States").describe(`full name of the location required field only in format "Country" (not "City" or "Region") example: 'United Kingdom', 'United States', 'Canada'`), language_code: z.string().default("en").describe( `language code required field example: en`), ignore_synonyms: z.boolean().default(true).describe( `ignore highly similar keywords, if set to true, results will be more accurate`), limit: z.number().min(1).max(1000).default(10).optional().describe("Maximum number of keywords to return"), offset: z.number().min(0).optional().describe( `offset in the results array of returned keywords optional field default value: 0 if you specify the 10 value, the first ten keywords in the results array will be omitted and the data will be provided for the successive keywords` ), filters: this.getFilterExpression().optional().describe( `you can add several filters at once (8 filters maximum) you should set a logical operator and, or between the conditions the following operators are supported: regex, not_regex, <, <=, >, >=, =, <>, in, not_in, match, not_match, ilike, not_ilike, like, not_like you can use the % operator with like and not_like, as well as ilike and not_ilike to match any string of zero or more characters merge operator must be a string and connect two other arrays, availible values: or, and. example: ["metrics.organic.count",">",50] [["metrics.organic.pos_1","<>",0],"and",["metrics.organic.impressions_etv",">=","10"]] [[["metrics.organic.count",">=",50],"and",["metrics.organic.pos_1","in",[1,5]]], "or", ["metrics.organic.etv",">=","100"]]` ), order_by: z.array(z.string()).optional().describe( `results sorting rules optional field you can use the same values as in the filters array to sort the results possible sorting types: asc – results will be sorted in the ascending order desc – results will be sorted in the descending order you should use a comma to specify a sorting type example: ["metrics.paid.etv,asc"] Note: you can set no more than three sorting rules in a single request you should use a comma to separate several sorting rules example: ["metrics.organic.etv,desc","metrics.paid.count,asc"] default rule: ["metrics.organic.count,desc"]` ), exclude_top_domains: z.boolean().default(true).describe(`indicates whether to exclude world’s largest websites optional field default value: false set to true if you want to get highly-relevant competitors excluding the top websites`) , item_types: z.array(z.string()).optional().describe( `item types to return optional field default: ['organic'] possible values: organic paid` ), include_clickstream_data: z.boolean().optional().default(false).describe( `Include or exclude data from clickstream-based metrics in the result`) }; } async handle(params: any): Promise<any> { try { const response = await this.client.makeRequest('/v3/dataforseo_labs/google/relevant_pages/live', 'POST', [{ target: params.target, location_name: params.location_name, language_code: params.language_code, ignore_synonyms: params.ignore_synonyms, filters: this.formatFilters(params.filters), order_by: this.formatOrderBy(params.order_by), exclude_top_domains: params.exclude_top_domains, item_types: params.item_types, include_clickstream_data: params.include_clickstream_data, limit: params.limit, offset: params.offset }]); return this.validateAndFormatResponse(response); } catch (error) { return this.formatErrorResponse(error); } } } ``` -------------------------------------------------------------------------------- /src/core/modules/base.tool.ts: -------------------------------------------------------------------------------- ```typescript import { z } from 'zod'; import { DataForSEOClient } from '../client/dataforseo.client.js'; import { defaultGlobalToolConfig } from '../config/global.tool.js'; import { filterFields, parseFieldPaths } from '../utils/field-filter.js'; import { FieldConfigurationManager } from '../config/field-configuration.js'; export interface DataForSEOFullResponse { version: string; status_code: number; status_message: string; time: string; cost: number; tasks_count: number; tasks_error: number; tasks: Array<{ id: string; status_code: number; status_message: string; time: string; cost: number; result_count: number; path: string[]; data: Record<string, any>; result: any[]; }>; } export interface DataForSEOResponse { id: string; status_code: number; status_message: string; items: any[]; } export abstract class BaseTool { protected dataForSEOClient: DataForSEOClient; constructor(dataForSEOClient: DataForSEOClient) { this.dataForSEOClient = dataForSEOClient; } protected supportOnlyFullResponse(): boolean { return false; } protected formatError(error: unknown): string { return error instanceof Error ? error.message : 'Unknown error'; } protected getFilterExpression(): z.ZodType<any> { if( defaultGlobalToolConfig.simpleFilter ) { return z.array(z.any()); } const filterExpression = z.array( z.union([ z.array(z.union([z.string(), z.number(), z.boolean()])).length(3), z.enum(["and", "or"]), z.array(z.unknown()).length(3), z.union([z.string(), z.number(),z.unknown()]), z.any() ]) ).max(3); return filterExpression; } protected validateAndFormatResponse(response: any): { content: Array<{ type: string; text: string }> } { console.error(JSON.stringify(response)); if (defaultGlobalToolConfig.fullResponse || this.supportOnlyFullResponse()) { let data = response as DataForSEOFullResponse; this.validateResponseFull(data); let result = data.tasks[0].result; return this.formatResponse(result); } this.validateResponse(response); return this.formatResponse(response); } protected formatResponse(data: any): { content: Array<{ type: string; text: string }> } { const fieldConfig = FieldConfigurationManager.getInstance(); if (fieldConfig.hasConfiguration()) { const toolName = this.getName(); if (fieldConfig.isToolConfigured(toolName)) { const fields = fieldConfig.getFieldsForTool(toolName); if (fields && fields.length > 0) { data = filterFields(data, parseFieldPaths(fields)); } } } return { content: [ { type: "text", text: JSON.stringify(data, null, 2), }, ], }; } protected formatErrorResponse(error: unknown): { content: Array<{ type: string; text: string }> } { return { content: [ { type: "text", text: `Error: ${this.formatError(error)}`, }, ], }; } protected validateResponse(response: DataForSEOResponse): void { if (response.status_code / 100 !== 200) { throw new Error(`API Error: ${response.status_message} (Code: ${response.status_code})`); } } protected validateResponseFull(response: DataForSEOFullResponse): void { if (response.status_code / 100 !== 200) { throw new Error(`API Error: ${response.status_message} (Code: ${response.status_code})`); } if (response.tasks.length === 0) { throw new Error('No tasks in response'); } const task = response.tasks[0]; if (task.status_code / 100 !== 200) { throw new Error(`Task Error: ${task.status_message} (Code: ${task.status_code})`); } if (response.tasks_error > 0) { throw new Error(`Tasks Error: ${response.tasks_error} tasks failed`); } } abstract getName(): string; abstract getDescription(): string; abstract getParams(): z.ZodRawShape; abstract handle(params: any): Promise<any>; protected filterResponseFields(response: any, fields: string[]): any { if (!fields || fields.length === 0) { return response; } const fieldPaths = parseFieldPaths(fields); return filterFields(response, fieldPaths); } protected formatFilters(filters: any[]): any { if (!filters) return null; if (filters.length === 0) { return null; } return this.removeNested(filters); } private removeNested(filters: any[]): any[] { for (var i = 0; i < filters.length; i++) { if (Array.isArray(filters[i]) && filters[i].length == 1 && Array.isArray(filters[i][0])) { filters[i] = this.removeNested(filters[i][0]); } } return filters; } protected formatOrderBy(orderBy: any[]): any { if (!orderBy) return null; if (orderBy.length === 0) { return null; } return orderBy; } } ``` -------------------------------------------------------------------------------- /src/core/modules/dataforseo-labs/tools/google/competitor-research/google-domain-intersection.tool.ts: -------------------------------------------------------------------------------- ```typescript import { z } from 'zod'; import { DataForSEOClient } from '../../../../../client/dataforseo.client.js'; import { BaseTool } from '../../../../base.tool.js'; export class GoogleDomainIntersectionsTool extends BaseTool { constructor(private client: DataForSEOClient) { super(client); } getName(): string { return 'dataforseo_labs_google_domain_intersection'; } getDescription(): string { return `This endpoint will provide you with the keywords for which both specified domains rank within the same SERP. You will get search volume, competition, cost-per-click and impressions data on each intersecting keyword. Along with that, you will get data on the first and second domain's SERP element discovered for this keyword, as well as the estimated traffic volume and cost of ad traffic.`; } getParams(): z.ZodRawShape { return { target1: z.string().describe(`target domain 1`), target2: z.string().describe(`target domain 2 `), location_name: z.string().default("United States").describe(`full name of the location required field only in format "Country" (not "City" or "Region") example: 'United Kingdom', 'United States', 'Canada'`), language_code: z.string().default("en").describe( `language code required field example: en`), ignore_synonyms: z.boolean().default(true).describe( `ignore highly similar keywords, if set to true, results will be more accurate`), limit: z.number().min(1).max(1000).default(10).optional().describe("Maximum number of keywords to return"), offset: z.number().min(0).optional().describe( `offset in the results array of returned keywords optional field default value: 0 if you specify the 10 value, the first ten keywords in the results array will be omitted and the data will be provided for the successive keywords` ), filters: this.getFilterExpression().optional().describe( `you can add several filters at once (8 filters maximum) you should set a logical operator and, or between the conditions the following operators are supported: regex, not_regex, <, <=, >, >=, =, <>, in, not_in, match, not_match, ilike, not_ilike, like, not_like you can use the % operator with like and not_like, as well as ilike and not_ilike to match any string of zero or more characters merge operator must be a string and connect two other arrays, availible values: or, and. example: ["keyword_data.keyword_info.search_volume","in",[100,1000]] [["first_domain_serp_element.etv",">",0],"and",["first_domain_serp_element.description","like","%goat%"]] [["keyword_data.keyword_info.search_volume",">",100],"and",[["first_domain_serp_element.description","like","%goat%"],"or",["second_domain_serp_element.type","=","organic"]]]` ), order_by: z.array(z.string()).optional().describe( `results sorting rules optional field you can use the same values as in the filters array to sort the results possible sorting types: asc – results will be sorted in the ascending order desc – results will be sorted in the descending order you should use a comma to set up a sorting parameter example: ["keyword_data.keyword_info.competition,desc"] default rule: ["keyword_data.keyword_info.search_volume,desc"] note that you can set no more than three sorting rules in a single request you should use a comma to separate several sorting rules example: ["keyword_data.keyword_info.search_volume,desc","keyword_data.keyword_info.cpc,desc"]` ), intersections: z.boolean().optional().describe(`domain intersections in SERP optional field if you set intersections to true, you will get the keywords for which both target domains specified as target1 and target2 have results within the same SERP; the corresponding SERP elements for both domains will be provided in the results array Note: this endpoint will not provide results if the number of intersecting keywords exceeds 10 million if you specify intersections: false, you will get the keywords for which the domain specified as target1 has results in SERP, and the domain specified as target2 doesn’t; thus, the corresponding SERP elements and other data will be provided for the domain specified as target1only default value: true`).default(true), include_clickstream_data: z.boolean().optional().default(false).describe( `Include or exclude data from clickstream-based metrics in the result`) }; } async handle(params: any): Promise<any> { try { const response = await this.client.makeRequest('/v3/dataforseo_labs/google/domain_intersection/live', 'POST', [{ target1: params.target1, target2: params.target2, location_name: params.location_name, language_code: params.language_code, ignore_synonyms: params.ignore_synonyms, filters: this.formatFilters(params.filters), order_by: this.formatOrderBy(params.order_by), exclude_top_domains: params.exclude_top_domains, item_types: ['organic'], intersections: params.intersections, include_clickstream_data: params.include_clickstream_data, limit: params.limit, offset: params.offset }]); return this.validateAndFormatResponse(response); } catch (error) { return this.formatErrorResponse(error); } } } ``` -------------------------------------------------------------------------------- /src/core/modules/dataforseo-labs/tools/labs-filters.tool.ts: -------------------------------------------------------------------------------- ```typescript import { z } from 'zod'; import { DataForSEOClient } from '../../../client/dataforseo.client.js'; import { BaseTool, DataForSEOFullResponse } from '../../base.tool.js'; interface FilterField { type: string; path: string; } interface ToolFilters { [key: string]: { [engine: string]: { [field: string]: string; }; }; } export class DataForSeoLabsFilterTool extends BaseTool { private static cache: ToolFilters | null = null; private static lastFetchTime: number = 0; private static readonly CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours in milliseconds // Map of tool names to their corresponding filter paths in the API response private static readonly TOOL_TO_FILTER_MAP: { [key: string]: string } = { 'dataforseo_labs_google_ranked_keywords': 'ranked_keywords.google', 'dataforseo_labs_google_keyword_ideas': 'keyword_ideas.google', 'dataforseo_labs_google_keywords_for_site': 'keywords_for_site.google', 'dataforseo_labs_google_competitors_domain': 'competitors_domain.google', 'dataforseo_labs_google_serp_competitors': 'serp_competitors.google', 'dataforseo_labs_google_subdomains': 'subdomains.google', 'dataforseo_labs_google_domain_intersection': 'domain_intersection.google', 'dataforseo_labs_google_page_intersection': 'page_intersection.google', 'dataforseo_labs_google_historical_serp': 'historical_serp.google', 'dataforseo_labs_google_historical_rank_overview': 'domain_rank_overview.google', 'dataforseo_labs_google_relevant_pages': 'relevant_pages.google', 'dataforseo_labs_google_top_searches': 'top_searches.google', 'dataforseo_labs_google_keyword_overview': 'keyword_overview.google', 'dataforseo_labs_google_search_intent': 'search_intent.google', 'dataforseo_labs_google_bulk_keyword_difficulty': 'bulk_keyword_difficulty.google', 'dataforseo_labs_google_related_keywords': 'related_keywords.google', 'dataforseo_labs_google_keyword_suggestions': 'keyword_suggestions.google', 'dataforseo_labs_google_domain_rank_overview': 'domain_rank_overview.google', 'dataforseo_labs_google_domain_metrics_by_categories': 'domain_metrics_by_categories.google', 'dataforseo_labs_google_domain_whois_overview': 'domain_whois_overview.google', 'dataforseo_labs_google_categories_for_domain': 'categories_for_domain.google', 'dataforseo_labs_google_keywords_for_categories': 'keywords_for_categories.google', 'dataforseo_labs_amazon_product_competitors': 'product_competitors.amazon', 'dataforseo_labs_amazon_product_keyword_intersections': 'product_keyword_intersections.amazon', 'dataforseo_labs_google_app_competitors': 'app_competitors.google', 'dataforseo_labs_apple_app_competitors': 'app_competitors.apple', 'dataforseo_labs_google_app_intersection': 'app_intersection.google', 'dataforseo_labs_apple_app_intersection': 'app_intersection.apple', 'dataforseo_labs_google_keywords_for_app': 'keywords_for_app.google', 'dataforseo_labs_apple_keywords_for_app': 'keywords_for_app.apple', 'dataforseo_labs_database_rows_count': 'database_rows_count' }; constructor(private client: DataForSEOClient) { super(client); } getName(): string { return 'dataforseo_labs_available_filters'; } getDescription(): string { return `Here you will find all the necessary information about filters that can be used with DataForSEO Labs API endpoints. Please, keep in mind that filters are associated with a certain object in the result array, and should be specified accordingly.`; } protected supportOnlyFullResponse(): boolean { return true; } getParams(): z.ZodRawShape { return { tool: z.string().optional().describe('The name of the tool to get filters for') }; } private async fetchAndCacheFilters(): Promise<ToolFilters> { const now = Date.now(); // Return cached data if it's still valid if (DataForSeoLabsFilterTool.cache && (now - DataForSeoLabsFilterTool.lastFetchTime) < DataForSeoLabsFilterTool.CACHE_TTL) { return DataForSeoLabsFilterTool.cache; } // Fetch fresh data const response = await this.client.makeRequest('/v3/dataforseo_labs/available_filters', 'GET', null, true) as DataForSEOFullResponse; this.validateResponseFull(response); // Transform the response into our cache format const filters: ToolFilters = {}; const result = response.tasks[0].result[0]; // Process each tool's filters for (const [toolName, filterPath] of Object.entries(DataForSeoLabsFilterTool.TOOL_TO_FILTER_MAP)) { const pathParts = filterPath.split('.'); let current = result; // Navigate to the correct filter object for (const part of pathParts) { if (current && current[part]) { current = current[part]; } else { current = null; break; } } if (current) { filters[toolName] = current; } } // Update cache DataForSeoLabsFilterTool.cache = filters; DataForSeoLabsFilterTool.lastFetchTime = now; return filters; } async handle(params: any): Promise<any> { try { const filters = await this.fetchAndCacheFilters(); if (!params.tool) { return this.formatResponse(filters); } const toolFilters = filters[params.tool]; if (!toolFilters) { throw new Error(`No filters found for tool: ${params.tool}`); } return this.formatResponse(toolFilters); } catch (error) { return this.formatErrorResponse(error); } } } ``` -------------------------------------------------------------------------------- /src/core/modules/dataforseo-labs/dataforseo-labs.prompts.ts: -------------------------------------------------------------------------------- ```typescript import { join } from 'path'; import { z } from 'zod'; import { PromptDefinition } from '../prompt-definition.js'; export const datalabsPrompts: PromptDefinition[] = [ { name: 'create_content_targeting_decision_stage_users', title: 'Create content targeting decision-stage users', params: { product: z.string().describe('The product to search related keywords for') }, handler: async (params) => { return { messages: [ { role: 'user', content: { type: 'text', text: `What are alternative or comparison ("vs", "alternative", "best", "compare") search queries people use for ${params.product}? Return 20 ideas with high search volume.` } } ] }; } }, { name: 'generate_seo_friendly_article_ideas', title: 'Generate SEO-friendly article ideas that directly answer user questions.', params: { topic: z.string().describe('topic'), }, handler: async (params) => { return { messages: [ { role: 'user', content: { type: 'text', text: `Show me 20 question-based keywords (what, why, how) for '${params.topic}' with search volume ≥ 300. Include search intent and suggest article headlines that match the query tone.` } } ] }; } }, { name: 'focus_on_high_converting_terms_for_paid_campaigns_based_on_buyer_readiness', title: 'Focus on high-converting terms for paid campaigns based on buyer readiness.', params: { product: z.string().describe('The product/service to compare'), }, handler: async (params) => { return { messages: [ { role: 'user', content: { type: 'text', text: `Find 20 commercial and transactional keywords related to '${params.product}'. Filter by CPC ≥ $2 and search volume ≥ 1,000. Suggest landing page angles based on intent.` } } ] }; } }, { name: 'structure_site_content_and_internal_linking_based_on_keyword_clusters', title: 'Structure site content and internal linking based on keyword clusters.', params: { keyword: z.string().describe('The keyword to cluster related keywords for'), }, handler: async (params) => { return { messages: [ { role: 'user', content: { type: 'text', text: `Provide a 20-term keyword cluster around ${params.keyword}. Group them by intent (informational, commercial, transactional) and list keyword difficulty and SERP features.` } } ] }; } }, { name: 'compare_sites_by_keywords', title: 'Competitor Comparison', params: { site_1: z.string().describe('The first site to compare'), site_2: z.string().describe('The second site to compare'), }, handler: async (params) => { return { messages: [ { role: 'user', content: { type: 'text', text: `Create a competitor comparison matrix between ${params.site_1} and ${params.site_2} based on keyword overlap and backlink profile.` } } ] }; } }, { name: 'build_content_that_aligns_with_user_research_behavior_and_ranks_easier', title: 'Build content that aligns with user research behavior and ranks easier.', params: { topic: z.string().describe('keyword/topic'), }, handler: async (params) => { return { messages: [ { role: 'user', content: { type: 'text', text: `Give me 20 informational keywords around '${params.topic}' with low competition and moderate search volume (≥500). Group them by intent and suggest blog topics for each..` } } ] }; } }, { name: 'track_long_term_seo_performance', title: 'Track long-term SEO performance, visibility shifts, and seasonal trends.', params: { domain: z.string().describe('The domain to analyze'), location: z.string().describe('The location to analyze'), language: z.string().describe('The language to analyze'), }, handler: async (params) => { return { messages: [ { role: 'user', content: { type: 'text', text: `Using google_historical_rank_overview in DataForSEO Labs API, show how the visibility and SERP position distribution of '${params.domain}' changed in ${params.location} in ${params.language} over the past 12 months. Focus on the top 3, top 10, and top 100 rankings, and highlight any traffic peaks.` } } ] }; } }, { name: 'compare_monthly_organic_traffic_trends_and_ranking_distribution_against_a_competitor', title: 'Compare monthly organic traffic trends and ranking distribution against a competitor.', params: { domain: z.string().describe('Your domain to analyze'), competitor_domain: z.string().describe('Competitor domain to compare against'), location: z.string().describe('The location to analyze'), language: z.string().describe('The language to analyze'), }, handler: async (params) => { return { messages: [ { role: 'user', content: { type: 'text', text: `Compare the monthly organic traffic trends and ranking distribution of ${params.domain} vs ${params.competitor_domain} in ${params.location} in ${params.language} using google_domain_rank_overview. Highlight who has better top 10 visibility and estimated traffic this month.` } } ] }; } }, ]; ``` -------------------------------------------------------------------------------- /src/main/index-http.ts: -------------------------------------------------------------------------------- ```typescript #!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { DataForSEOClient, DataForSEOConfig } from '../core/client/dataforseo.client.js'; import { SerpApiModule } from '../core/modules/serp/serp-api.module.js'; import { KeywordsDataApiModule } from '../core/modules/keywords-data/keywords-data-api.module.js'; import { OnPageApiModule } from '../core/modules/onpage/onpage-api.module.js'; import { DataForSEOLabsApi } from '../core/modules/dataforseo-labs/dataforseo-labs-api.module.js'; import { EnabledModulesSchema, isModuleEnabled, defaultEnabledModules } from '../core/config/modules.config.js'; import { BaseModule, ToolDefinition } from '../core/modules/base.module.js'; import { z } from 'zod'; import { BacklinksApiModule } from "../core/modules/backlinks/backlinks-api.module.js"; import { BusinessDataApiModule } from "../core/modules/business-data-api/business-data-api.module.js"; import { DomainAnalyticsApiModule } from "../core/modules/domain-analytics/domain-analytics-api.module.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import express, { Request as ExpressRequest, Response, NextFunction } from "express"; import { randomUUID } from "node:crypto"; import { GetPromptResult, isInitializeRequest, ReadResourceResult, ServerNotificationSchema } from "@modelcontextprotocol/sdk/types.js" import { name, version } from '../core/utils/version.js'; import { ModuleLoaderService } from "../core/utils/module-loader.js"; import { initializeFieldConfiguration } from '../core/config/field-configuration.js'; import { initMcpServer } from "./init-mcp-server.js"; // Initialize field configuration if provided initializeFieldConfiguration(); // Extended request interface to include auth properties interface Request extends ExpressRequest { username?: string; password?: string; } console.error('Starting DataForSEO MCP Server...'); console.error(`Server name: ${name}, version: ${version}`); function getSessionId() { return randomUUID().toString(); } async function main() { const app = express(); app.use(express.json()); // Basic Auth Middleware const basicAuth = (req: Request, res: Response, next: NextFunction) => { // Check for Authorization header const authHeader = req.headers.authorization; console.error(authHeader) if (!authHeader || !authHeader.startsWith('Basic ')) { next(); return; } // Extract credentials const base64Credentials = authHeader.split(' ')[1]; const credentials = Buffer.from(base64Credentials, 'base64').toString('utf-8'); const [username, password] = credentials.split(':'); if (!username || !password) { console.error('Invalid credentials'); res.status(401).json({ jsonrpc: "2.0", error: { code: -32001, message: "Invalid credentials" }, id: null }); return; } // Add credentials to request req.username = username; req.password = password; next(); }; const handleMcpRequest = async (req: Request, res: Response) => { // In stateless mode, create a new instance of transport and server for each request // to ensure complete isolation. A single instance would cause request ID collisions // when multiple clients connect concurrently. try { // Check if we have valid credentials if (!req.username && !req.password) { // If no request auth, check environment variables const envUsername = process.env.DATAFORSEO_USERNAME; const envPassword = process.env.DATAFORSEO_PASSWORD; if (!envUsername || !envPassword) { console.error('No DataForSEO credentials provided'); res.status(401).json({ jsonrpc: "2.0", error: { code: -32001, message: "Authentication required. Provide DataForSEO credentials." }, id: null }); return; } // Use environment variables req.username = envUsername; req.password = envPassword; } const server = initMcpServer(req.username, req.password); console.error(Date.now().toLocaleString()) const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined }); await server.connect(transport); console.error('handle request'); await transport.handleRequest(req , res, req.body); console.error('end handle request'); req.on('close', () => { console.error('Request closed'); transport.close(); server.close(); }); } catch (error) { console.error('Error handling MCP request:', error); if (!res.headersSent) { res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error', }, id: null, }); } } }; const handleNotAllowed = (method: string) => async (req: Request, res: Response) => { console.error(`Received ${method} request`); res.status(405).json({ jsonrpc: "2.0", error: { code: -32000, message: "Method not allowed." }, id: null }); }; // Apply basic auth and shared handler to both endpoints app.post('/http', basicAuth, handleMcpRequest); app.post('/mcp', basicAuth, handleMcpRequest); app.get('/http', handleNotAllowed('GET HTTP')); app.get('/mcp', handleNotAllowed('GET MCP')); app.delete('/http', handleNotAllowed('DELETE HTTP')); app.delete('/mcp', handleNotAllowed('DELETE MCP')); // Start the server const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000; app.listen(PORT, () => { console.log(`MCP Stateless Streamable HTTP Server listening on port ${PORT}`); }); } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); }); ``` -------------------------------------------------------------------------------- /src/core/modules/dataforseo-labs/tools/google/competitor-research/google-page-intersection.tool.ts: -------------------------------------------------------------------------------- ```typescript import { z } from 'zod'; import { DataForSEOClient } from '../../../../../client/dataforseo.client.js'; import { BaseTool } from '../../../../base.tool.js'; import { mapArrayToNumberedKeys } from '../../../../../utils/map-array-to-numbered-keys.js'; export class GooglePageIntersectionsTool extends BaseTool { constructor(private client: DataForSEOClient) { super(client); } getName(): string { return 'dataforseo_labs_google_page_intersection'; } getDescription(): string { return `This endpoint will provide you with the keywords for which specified pages rank within the same SERP. You will get search volume, competition, cost-per-click and impressions data on each intersecting keyword. Along with that, you will get data on SERP elements that specified pages rank for in search results, as well as the estimated traffic volume and cost of ad traffic. Page Intersection endpoint supports organic, paid, local pack and featured snippet results. Find keywords several webpages rank for: If you would like to get the keywords several pages rank for, you need to specify webpages only in the pages object. This way, you will receive intersected ranked keywords for the specified URLs. Find keywords your competitors rank for but you do not: If you would like to receive all keywords several pages rank for, but particular pages do not, you need to use the exclude_pages array as well. This way you will receive the keywords for which the URLs from the pages object rank for, but the URLs from the exclude_pages array do not`; } getParams(): z.ZodRawShape { return { pages: z.array(z.string()).describe(`pages array required field you can set up to 20 pages in this object the pages should be specified with absolute URLs (including http:// or https://) if you specify a single page here, we will return results only for this page; you can also use a wildcard ('*') character to specify the search pattern example: "example.com" search for the exact URL "example.com/eng/*" search for the example.com page and all its related URLs which start with '/eng/', such as "example.com/eng/index.html" and "example.com/eng/help/", etc. note: a wilcard should be placed after the slash ('/') character in the end of the URL, it is not possible to place it after the domain in the following way: https://dataforseo.com* use https://dataforseo.com/* instead`), exclude_pages: z.array(z.string()).optional().describe(`URLs of pages you want to exclude optional field you can set up to 10 pages in this array if you use this array, results will contain the keywords for which URLs from the pages object rank, but URLs from exclude_pages array do not; note that if you specify this field, the results will be based on the keywords any URL from pages ranks for regardless of intersections between them. However, you can set intersection_mode to intersect and results will contain the keywords all URLs from pages rank for in the same SERP and URLs from exclude_pages do not. use a wildcard (‘*’) character to specify the search pattern example: "exclude_pages": [ "https://www.apple.com/iphone/*", "https://dataforseo.com/apis/*", "https://www.microsoft.com/en-us/industry/services/" ]`), intersection_mode: z.enum(['union', 'intersect']).optional().describe(`indicates whether to intersect keywords optional field use this field to intersect or merge results for the specified URLs possible values: union, intersect union – results are based on all keywords any URL from pages rank for; intersect – results are based on the keywords all URLs from pages rank for in the same SERP: by default, results are based on the intersect mode if you specify only pages array. If you specify exclude_pages as well, results are based on the union mode`), location_name: z.string().default("United States").describe(`full name of the location required field only in format "Country" (not "City" or "Region") example: 'United Kingdom', 'United States', 'Canada'`), language_code: z.string().default("en").describe( `language code required field example: en`), ignore_synonyms: z.boolean().default(true).describe( `ignore highly similar keywords, if set to true, results will be more accurate`), limit: z.number().min(1).max(1000).default(10).optional().describe("Maximum number of keywords to return"), offset: z.number().min(0).optional().describe( `offset in the results array of returned keywords optional field default value: 0 if you specify the 10 value, the first ten keywords in the results array will be omitted and the data will be provided for the successive keywords` ), filters: this.getFilterExpression().optional().describe( `you can add several filters at once (8 filters maximum) you should set a logical operator and, or between the conditions the following operators are supported: regex, not_regex, <, <=, >, >=, =, <>, in, not_in, match, not_match, ilike, not_ilike, like, not_like you can use the % operator with like and not_like, as well as ilike and not_ilike to match any string of zero or more characters merge operator must be a string and connect two other arrays, availible values: or, and. example: ["keyword_data.keyword_info.search_volume","in",[100,1000]] [["intersection_result.1.etv",">",0],"and",["intersection_result.2.description","like","%goat%"]] [["keyword_data.keyword_info.search_volume",">",100],"and",[["intersection_result.1.description","like","%goat%"],"or",["intersection_result.2.type","=","organic"]]]` ), order_by: z.array(z.string()).optional().describe( `results sorting rules optional field you can use the same values as in the filters array to sort the results possible sorting types: asc – results will be sorted in the ascending order desc – results will be sorted in the descending order you should use a comma to set up a sorting parameter example: ["keyword_data.keyword_info.competition,desc"] default rule: ["keyword_data.keyword_info.search_volume,desc"] note that you can set no more than three sorting rules in a single request you should use a comma to separate several sorting rules example: ["intersection_result.1.rank_group,asc","intersection_result.2.rank_absolute,asc"]` ), include_clickstream_data: z.boolean().optional().default(false).describe( `Include or exclude data from clickstream-based metrics in the result`) }; } async handle(params: any): Promise<any> { try { const response = await this.client.makeRequest('/v3/dataforseo_labs/google/page_intersection/live', 'POST', [{ pages: mapArrayToNumberedKeys(params.pages), location_name: params.location_name, language_code: params.language_code, ignore_synonyms: params.ignore_synonyms, filters: this.formatFilters(params.filters), order_by: this.formatOrderBy(params.order_by), exclude_top_domains: params.exclude_top_domains, item_types: ['organic'], exclude_pages: params.exclude_pages, intersection_mode: params.intersection_mode, limit: params.limit, offset: params.offset, include_clickstream_data: params.include_clickstream_data }]); return this.validateAndFormatResponse(response); } catch (error) { return this.formatErrorResponse(error); } } } ``` -------------------------------------------------------------------------------- /src/main/index-sse-http.ts: -------------------------------------------------------------------------------- ```typescript import express, { Request as ExpressRequest, Response, NextFunction } from 'express'; import { randomUUID } from "node:crypto"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { z } from 'zod'; import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"; import { DataForSEOClient, DataForSEOConfig } from '../core/client/dataforseo.client.js'; import { EnabledModulesSchema, isModuleEnabled } from '../core/config/modules.config.js'; import { BaseModule, ToolDefinition } from '../core/modules/base.module.js'; import { name, version } from '../core/utils/version.js'; import { InMemoryEventStore } from '@modelcontextprotocol/sdk/examples/shared/inMemoryEventStore.js'; import { ModuleLoaderService } from '../core/utils/module-loader.js'; import { initializeFieldConfiguration } from '../core/config/field-configuration.js'; import { initMcpServer } from './init-mcp-server.js'; // Initialize field configuration if provided initializeFieldConfiguration(); console.error('Starting DataForSEO MCP Server...'); console.error(`Server name: ${name}, version: ${version}`); /** * This example server demonstrates backwards compatibility with both: * 1. The deprecated HTTP+SSE transport (protocol version 2024-11-05) * 2. The Streamable HTTP transport (protocol version 2025-03-26) * * It maintains a single MCP server instance but exposes two transport options: * - /mcp: The new Streamable HTTP endpoint (supports GET/POST/DELETE) * - /sse: The deprecated SSE endpoint for older clients (GET to establish stream) * - /messages: The deprecated POST endpoint for older clients (POST to send messages) */ // Configuration constants const CONNECTION_TIMEOUT = 30000; // 30 seconds const CLEANUP_INTERVAL = 60000; // 1 minute // Extended request interface to include auth properties interface Request extends ExpressRequest { username?: string; password?: string; } // Transport interface with timestamp interface TransportWithTimestamp { transport: StreamableHTTPServerTransport | SSEServerTransport; lastActivity: number; } // Store transports by session ID const transports: Record<string, TransportWithTimestamp> = {}; // Cleanup function for stale connections function cleanupStaleConnections() { const now = Date.now(); Object.entries(transports).forEach(([sessionId, { transport, lastActivity }]) => { if (now - lastActivity > CONNECTION_TIMEOUT) { console.log(`Cleaning up stale connection for session ${sessionId}`); try { transport.close(); } catch (error) { console.error(`Error closing transport for session ${sessionId}:`, error); } delete transports[sessionId]; } }); } // Start periodic cleanup const cleanupInterval = setInterval(cleanupStaleConnections, CLEANUP_INTERVAL); // Create Express application const app = express(); app.use(express.json()); // Basic Auth Middleware const basicAuth = (req: Request, res: Response, next: NextFunction) => { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Basic ')) { next(); return; } const base64Credentials = authHeader.split(' ')[1]; const credentials = Buffer.from(base64Credentials, 'base64').toString('utf-8'); const [username, password] = credentials.split(':'); if (!username || !password) { console.error('Invalid credentials'); res.status(401).json({ jsonrpc: "2.0", error: { code: -32001, message: "Invalid credentials" }, id: null }); return; } req.username = username; req.password = password; next(); }; //============================================================================= // STREAMABLE HTTP TRANSPORT (PROTOCOL VERSION 2025-03-26) //============================================================================= const handleMcpRequest = async (req: Request, res: Response) => { // In stateless mode, create a new instance of transport and server for each request // to ensure complete isolation. A single instance would cause request ID collisions // when multiple clients connect concurrently. try { console.error(Date.now().toLocaleString()) // Handle credentials if (!req.username && !req.password) { const envUsername = process.env.DATAFORSEO_USERNAME; const envPassword = process.env.DATAFORSEO_PASSWORD; if (!envUsername || !envPassword) { console.error('No DataForSEO credentials provided'); res.status(401).json({ jsonrpc: "2.0", error: { code: -32001, message: "Authentication required. Provide DataForSEO credentials." }, id: null }); return; } req.username = envUsername; req.password = envPassword; } const server = initMcpServer(req.username, req.password); console.error(Date.now().toLocaleString()) const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined }); await server.connect(transport); console.error('handle request'); await transport.handleRequest(req , res, req.body); console.error('end handle request'); req.on('close', () => { console.error('Request closed'); transport.close(); server.close(); }); } catch (error) { console.error('Error handling MCP request:', error); if (!res.headersSent) { res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error', }, id: null, }); } } }; const handleNotAllowed = (method: string) => async (req: Request, res: Response) => { console.error(`Received ${method} request`); res.status(405).json({ jsonrpc: "2.0", error: { code: -32000, message: "Method not allowed." }, id: null }); }; // Apply basic auth and shared handler to both endpoints app.post('/http', basicAuth, handleMcpRequest); app.post('/mcp', basicAuth, handleMcpRequest); app.get('/http', handleNotAllowed('GET HTTP')); app.get('/mcp', handleNotAllowed('GET MCP')); app.delete('/http', handleNotAllowed('DELETE HTTP')); app.delete('/mcp', handleNotAllowed('DELETE MCP')); //============================================================================= // DEPRECATED HTTP+SSE TRANSPORT (PROTOCOL VERSION 2024-11-05) //============================================================================= app.get('/sse', basicAuth, async (req: Request, res: Response) => { console.log('Received GET request to /sse (deprecated SSE transport)'); // Handle credentials if (!req.username && !req.password) { const envUsername = process.env.DATAFORSEO_USERNAME; const envPassword = process.env.DATAFORSEO_PASSWORD; if (!envUsername || !envPassword) { console.error('No DataForSEO credentials provided'); res.status(401).json({ jsonrpc: "2.0", error: { code: -32001, message: "Authentication required. Provide DataForSEO credentials." }, id: null }); return; } req.username = envUsername; req.password = envPassword; } const transport = new SSEServerTransport('/messages', res); // Store transport with timestamp transports[transport.sessionId] = { transport, lastActivity: Date.now() }; // Handle connection cleanup const cleanup = () => { try { transport.close(); } catch (error) { console.error(`Error closing transport for session ${transport.sessionId}:`, error); } delete transports[transport.sessionId]; }; res.on("error", cleanup); req.on("error", cleanup); req.socket.on("error", cleanup); req.socket.on("timeout", cleanup); // Set socket timeout req.socket.setTimeout(CONNECTION_TIMEOUT); const server = initMcpServer(req.username, req.password); await server.connect(transport); }); app.post("/messages", basicAuth, async (req: Request, res: Response) => { const sessionId = req.query.sessionId as string; // Handle credentials if (!req.username && !req.password) { const envUsername = process.env.DATAFORSEO_USERNAME; const envPassword = process.env.DATAFORSEO_PASSWORD; if (!envUsername || !envPassword) { res.status(401).json({ jsonrpc: "2.0", error: { code: -32001, message: "Authentication required. Provide DataForSEO credentials." }, id: null }); return; } req.username = envUsername; req.password = envPassword; } const transportData = transports[sessionId]; if (!transportData) { res.status(400).send('No transport found for sessionId'); return; } if (!(transportData.transport instanceof SSEServerTransport)) { res.status(400).json({ jsonrpc: '2.0', error: { code: -32000, message: 'Bad Request: Session exists but uses a different transport protocol', }, id: null, }); return; } // Update last activity timestamp transportData.lastActivity = Date.now(); await transportData.transport.handlePostMessage(req, res, req.body); }); // Start the server const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000; const server = app.listen(PORT, () => { console.log(`DataForSEO MCP Server with SSE compatibility listening on port ${PORT}`); console.log(` ============================================== SUPPORTED TRANSPORT OPTIONS: 1. Streamable Http (Protocol version: 2025-03-26) Endpoint: /http (POST) Endpoint: /mcp (POST) 2. Http + SSE (Protocol version: 2024-11-05) Endpoints: /sse (GET) and /messages (POST) Usage: - Establish SSE stream with GET to /sse - Send requests with POST to /messages?sessionId=<id> ============================================== `); }); // Handle server shutdown process.on('SIGINT', async () => { console.log('Shutting down server...'); // Clear cleanup interval clearInterval(cleanupInterval); // Close HTTP server server.close(); // Close all active transports for (const sessionId in transports) { try { console.log(`Closing transport for session ${sessionId}`); await transports[sessionId].transport.close(); delete transports[sessionId]; } catch (error) { console.error(`Error closing transport for session ${sessionId}:`, error); } } console.log('Server shutdown complete'); process.exit(0); }); ``` -------------------------------------------------------------------------------- /field-config.example.json: -------------------------------------------------------------------------------- ```json { "supported_fields": { "serp_organic_live_advanced": [ "id", "items.asynchronous_ai_overview", "items.breadcrumb", "items.description", "items.domain", "items.highlighted", "items.is_video", "items.items.consideration_category", "items.items.data_attrid", "items.items.date", "items.items.description", "items.items.domain", "items.items.expanded_element.breadcrumb", "items.items.expanded_element.description", "items.items.expanded_element.domain", "items.items.expanded_element.featured_title", "items.items.expanded_element.items.references.domain", "items.items.expanded_element.items.references.source", "items.items.expanded_element.items.references.text", "items.items.expanded_element.items.references.title", "items.items.expanded_element.items.references.type", "items.items.expanded_element.items.references.url", "items.items.expanded_element.items.text", "items.items.expanded_element.items.title", "items.items.expanded_element.items.type", "items.items.expanded_element.references.domain", "items.items.expanded_element.references.source", "items.items.expanded_element.references.text", "items.items.expanded_element.references.title", "items.items.expanded_element.references.type", "items.items.expanded_element.references.url", "items.items.expanded_element.related_searches", "items.items.expanded_element.snippet", "items.items.expanded_element.timestamp", "items.items.expanded_element.title", "items.items.expanded_element.type", "items.items.expanded_element.url", "items.items.items.alt", "items.items.items.image_url", "items.items.items.type", "items.items.links.domain", "items.items.links.title", "items.items.links.type", "items.items.links.url", "items.items.source", "items.items.text", "items.items.timestamp", "items.items.title", "items.items.type", "items.items.url", "items.links.domain", "items.links.title", "items.links.type", "items.links.url", "items.pre_snippet", "items.rank_absolute", "items.rank_group", "items.subtitle", "items.timestamp", "items.title", "items.type", "items.url", "items.website_name", "status_code", "status_message" ], "keywords_data_google_ads_search_volume": [ "id", "items.competition", "items.competition_index", "items.cpc", "items.high_top_of_page_bid", "items.keyword", "items.low_top_of_page_bid", "items.monthly_searches", "items.search_volume", "status_code", "status_message" ], "keywords_data_dataforseo_trends_demography": [ "id", "items.demography.age.keyword", "items.demography.age.values.type", "items.demography.age.values.value", "items.demography.gender.keyword", "items.demography.gender.values.type", "items.demography.gender.values.value", "items.keywords", "items.type", "status_code", "status_message" ], "keywords_data_dataforseo_trends_subregion_interests": [ "id", "items.interests.keyword", "items.interests.values.geo_id", "items.interests.values.geo_name", "items.interests.values.value", "items.keywords", "items.type", "status_code", "status_message" ], "keywords_data_dataforseo_trends_explore": [ "id", "items.averages", "items.data.date_from", "items.data.date_to", "items.data.timestamp", "items.data.values", "items.keywords", "items.type", "status_code", "status_message" ], "keywords_data_google_trends_explore": [ "id", "items.data.date_from", "items.data.date_to", "items.data.missing_data", "items.data.timestamp", "items.data.values", "items.keywords", "items.type", "status_code", "status_message" ], "on_page_instant_pages": [ "id", "items.cache_control", "items.checks", "items.content_encoding", "items.encoded_size", "items.fetch_time", "items.last_modified.header", "items.media_type", "items.meta.charset", "items.meta.content.automated_readability_index", "items.meta.content.coleman_liau_readability_index", "items.meta.content.dale_chall_readability_index", "items.meta.content.flesch_kincaid_readability_index", "items.meta.content.plain_text_rate", "items.meta.content.plain_text_size", "items.meta.content.plain_text_word_count", "items.meta.content.smog_readability_index", "items.meta.content.title_to_content_consistency", "items.meta.cumulative_layout_shift", "items.meta.external_links_count", "items.meta.follow", "items.meta.htags.h1", "items.meta.title", "items.meta.title_length", "items.onpage_score", "items.page_timing.connection_time", "items.page_timing.dom_complete", "items.page_timing.duration_time", "items.page_timing.fetch_end", "items.page_timing.first_input_delay", "items.page_timing.largest_contentful_paint", "items.page_timing.time_to_interactive", "items.page_timing.time_to_secure_connection", "items.relative_url_length", "items.resource_type", "items.size", "items.status_code", "items.total_dom_size", "items.total_transfer_size", "items.url", "items.url_length", "status_code", "status_message" ], "dataforseo_labs_google_ranked_keywords": [ "id", "items.keyword_data.avg_backlinks_info.backlinks", "items.keyword_data.avg_backlinks_info.dofollow", "items.keyword_data.avg_backlinks_info.last_updated_time", "items.keyword_data.avg_backlinks_info.main_domain_rank", "items.keyword_data.avg_backlinks_info.rank", "items.keyword_data.avg_backlinks_info.referring_domains", "items.keyword_data.avg_backlinks_info.referring_main_domains", "items.keyword_data.avg_backlinks_info.referring_pages", "items.keyword_data.avg_backlinks_info.se_type", "items.keyword_data.keyword", "items.keyword_data.keyword_info.categories", "items.keyword_data.keyword_info.competition", "items.keyword_data.keyword_info.competition_level", "items.keyword_data.keyword_info.cpc", "items.keyword_data.keyword_info.high_top_of_page_bid", "items.keyword_data.keyword_info.last_updated_time", "items.keyword_data.keyword_info.low_top_of_page_bid", "items.keyword_data.keyword_info.monthly_searches", "items.keyword_data.keyword_info.se_type", "items.keyword_data.keyword_info.search_volume", "items.keyword_data.keyword_info.search_volume_trend.monthly", "items.keyword_data.keyword_info.search_volume_trend.quarterly", "items.keyword_data.keyword_info.search_volume_trend.yearly", "items.keyword_data.keyword_properties.core_keyword", "items.keyword_data.keyword_properties.detected_language", "items.keyword_data.keyword_properties.is_another_language", "items.keyword_data.keyword_properties.keyword_difficulty", "items.keyword_data.keyword_properties.se_type", "items.keyword_data.keyword_properties.synonym_clustering_algorithm", "items.keyword_data.language_code", "items.keyword_data.location_code", "items.keyword_data.se_type", "items.keyword_data.search_intent_info.foreign_intent", "items.keyword_data.search_intent_info.last_updated_time", "items.keyword_data.search_intent_info.main_intent", "items.keyword_data.search_intent_info.se_type", "items.keyword_data.serp_info.check_url", "items.keyword_data.serp_info.last_updated_time", "items.keyword_data.serp_info.previous_updated_time", "items.keyword_data.serp_info.se_results_count", "items.keyword_data.serp_info.se_type", "items.keyword_data.serp_info.serp_item_types", "items.ranked_serp_element.check_url", "items.ranked_serp_element.keyword_difficulty", "items.ranked_serp_element.last_updated_time", "items.ranked_serp_element.previous_updated_time", "items.ranked_serp_element.se_results_count", "items.ranked_serp_element.se_type", "items.ranked_serp_element.serp_item.backlinks_info.backlinks", "items.ranked_serp_element.serp_item.backlinks_info.dofollow", "items.ranked_serp_element.serp_item.backlinks_info.referring_domains", "items.ranked_serp_element.serp_item.backlinks_info.referring_main_domains", "items.ranked_serp_element.serp_item.backlinks_info.referring_pages", "items.ranked_serp_element.serp_item.backlinks_info.time_update", "items.ranked_serp_element.serp_item.breadcrumb", "items.ranked_serp_element.serp_item.description", "items.ranked_serp_element.serp_item.domain", "items.ranked_serp_element.serp_item.estimated_paid_traffic_cost", "items.ranked_serp_element.serp_item.etv", "items.ranked_serp_element.serp_item.highlighted", "items.ranked_serp_element.serp_item.main_domain", "items.ranked_serp_element.serp_item.rank_absolute", "items.ranked_serp_element.serp_item.rank_changes.is_down", "items.ranked_serp_element.serp_item.rank_changes.is_new", "items.ranked_serp_element.serp_item.rank_changes.previous_rank_absolute", "items.ranked_serp_element.serp_item.rank_group", "items.ranked_serp_element.serp_item.rank_info.main_domain_rank", "items.ranked_serp_element.serp_item.rank_info.page_rank", "items.ranked_serp_element.serp_item.relative_url", "items.ranked_serp_element.serp_item.se_type", "items.ranked_serp_element.serp_item.title", "items.ranked_serp_element.serp_item.type", "items.ranked_serp_element.serp_item.url", "items.ranked_serp_element.serp_item.website_name", "items.ranked_serp_element.serp_item_types", "items.se_type", "status_code", "status_message" ], "dataforseo_labs_google_competitors_domain": [ "id", "items.avg_position", "items.competitor_metrics.organic.count", "items.competitor_metrics.organic.estimated_paid_traffic_cost", "items.competitor_metrics.organic.etv", "items.competitor_metrics.organic.is_down", "items.competitor_metrics.organic.is_new", "items.competitor_metrics.organic.is_up", "items.competitor_metrics.organic.pos_1", "items.competitor_metrics.organic.pos_11_20", "items.competitor_metrics.organic.pos_21_30", "items.competitor_metrics.organic.pos_2_3", "items.competitor_metrics.organic.pos_31_40", "items.competitor_metrics.organic.pos_41_50", "items.competitor_metrics.organic.pos_4_10", "items.competitor_metrics.organic.pos_51_60", "items.competitor_metrics.organic.pos_61_70", "items.competitor_metrics.organic.pos_71_80", "items.competitor_metrics.organic.pos_81_90", "items.competitor_metrics.organic.pos_91_100", "items.domain", "items.full_domain_metrics.organic.count", "items.full_domain_metrics.organic.estimated_paid_traffic_cost", "items.full_domain_metrics.organic.etv", "items.full_domain_metrics.organic.is_down", "items.full_domain_metrics.organic.is_lost", "items.full_domain_metrics.organic.is_new", "items.full_domain_metrics.organic.is_up", "items.full_domain_metrics.organic.pos_1", "items.full_domain_metrics.organic.pos_11_20", "items.full_domain_metrics.organic.pos_21_30", "items.full_domain_metrics.organic.pos_2_3", "items.full_domain_metrics.organic.pos_31_40", "items.full_domain_metrics.organic.pos_41_50", "items.full_domain_metrics.organic.pos_4_10", "items.full_domain_metrics.organic.pos_51_60", "items.full_domain_metrics.organic.pos_61_70", "items.full_domain_metrics.organic.pos_71_80", "items.full_domain_metrics.organic.pos_81_90", "items.full_domain_metrics.organic.pos_91_100", "items.intersections", "items.metrics.*.count", "items.metrics.*.estimated_paid_traffic_cost", "items.metrics.*.etv", "items.metrics.*.is_down", "items.metrics.*.is_new", "items.metrics.*.is_up", "items.metrics.*.pos_1", "items.metrics.*.pos_11_20", "items.metrics.*.pos_21_30", "items.metrics.*.pos_2_3", "items.metrics.*.pos_31_40", "items.metrics.*.pos_41_50", "items.metrics.*.pos_4_10", "items.metrics.*.pos_51_60", "items.metrics.*.pos_61_70", "items.metrics.*.pos_71_80", "items.metrics.*.pos_81_90", "items.metrics.*.pos_91_100", "items.se_type", "items.sum_position", "status_code", "status_message" ], "dataforseo_labs_google_domain_rank_overview": [ "id", "items.language_code", "items.location_code", "items.metrics.*.count", "items.metrics.*.estimated_paid_traffic_cost", "items.metrics.*.etv", "items.metrics.*.is_down", "items.metrics.*.is_new", "items.metrics.*.is_up", "items.metrics.*.pos_1", "items.metrics.*.pos_11_20", "items.metrics.*.pos_21_30", "items.metrics.*.pos_2_3", "items.metrics.*.pos_31_40", "items.metrics.*.pos_41_50", "items.metrics.*.pos_4_10", "items.metrics.*.pos_51_60", "items.metrics.*.pos_61_70", "items.metrics.*.pos_71_80", "items.metrics.*.pos_81_90", "items.metrics.*.pos_91_100", "items.se_type", "status_code", "status_message" ], "dataforseo_labs_google_keyword_ideas": [ "id", "items.avg_backlinks_info.backlinks", "items.avg_backlinks_info.dofollow", "items.avg_backlinks_info.last_updated_time", "items.avg_backlinks_info.main_domain_rank", "items.avg_backlinks_info.rank", "items.avg_backlinks_info.referring_domains", "items.avg_backlinks_info.referring_main_domains", "items.avg_backlinks_info.referring_pages", "items.avg_backlinks_info.se_type", "items.keyword", "items.keyword_info.categories", "items.keyword_info.competition", "items.keyword_info.competition_level", "items.keyword_info.cpc", "items.keyword_info.high_top_of_page_bid", "items.keyword_info.last_updated_time", "items.keyword_info.low_top_of_page_bid", "items.keyword_info.monthly_searches", "items.keyword_info.se_type", "items.keyword_info.search_volume", "items.keyword_info.search_volume_trend.monthly", "items.keyword_info.search_volume_trend.quarterly", "items.keyword_info.search_volume_trend.yearly", "items.keyword_properties.core_keyword", "items.keyword_properties.detected_language", "items.keyword_properties.keyword_difficulty", "items.keyword_properties.se_type", "items.keyword_properties.synonym_clustering_algorithm", "items.language_code", "items.location_code", "items.se_type", "items.search_intent_info.foreign_intent", "items.search_intent_info.last_updated_time", "items.search_intent_info.main_intent", "items.search_intent_info.se_type", "status_code", "status_message" ], "dataforseo_labs_google_related_keywords": [ "id", "items.depth", "items.keyword_data.avg_backlinks_info.backlinks", "items.keyword_data.avg_backlinks_info.dofollow", "items.keyword_data.avg_backlinks_info.last_updated_time", "items.keyword_data.avg_backlinks_info.main_domain_rank", "items.keyword_data.avg_backlinks_info.rank", "items.keyword_data.avg_backlinks_info.referring_domains", "items.keyword_data.avg_backlinks_info.referring_main_domains", "items.keyword_data.avg_backlinks_info.referring_pages", "items.keyword_data.avg_backlinks_info.se_type", "items.keyword_data.keyword", "items.keyword_data.keyword_info.categories", "items.keyword_data.keyword_info.competition", "items.keyword_data.keyword_info.competition_level", "items.keyword_data.keyword_info.cpc", "items.keyword_data.keyword_info.high_top_of_page_bid", "items.keyword_data.keyword_info.last_updated_time", "items.keyword_data.keyword_info.low_top_of_page_bid", "items.keyword_data.keyword_info.monthly_searches", "items.keyword_data.keyword_info.se_type", "items.keyword_data.keyword_info.search_volume", "items.keyword_data.keyword_info.search_volume_trend.monthly", "items.keyword_data.keyword_info.search_volume_trend.quarterly", "items.keyword_data.keyword_info.search_volume_trend.yearly", "items.keyword_data.keyword_properties.core_keyword", "items.keyword_data.keyword_properties.detected_language", "items.keyword_data.keyword_properties.keyword_difficulty", "items.keyword_data.keyword_properties.se_type", "items.keyword_data.keyword_properties.synonym_clustering_algorithm", "items.keyword_data.language_code", "items.keyword_data.location_code", "items.keyword_data.se_type", "items.keyword_data.search_intent_info.foreign_intent", "items.keyword_data.search_intent_info.last_updated_time", "items.keyword_data.search_intent_info.main_intent", "items.keyword_data.search_intent_info.se_type", "items.keyword_data.serp_info.check_url", "items.keyword_data.serp_info.last_updated_time", "items.keyword_data.serp_info.previous_updated_time", "items.keyword_data.serp_info.se_results_count", "items.keyword_data.serp_info.se_type", "items.keyword_data.serp_info.serp_item_types", "items.related_keywords", "items.se_type", "status_code", "status_message" ], "dataforseo_labs_google_keyword_suggestions": [ "id", "items.avg_backlinks_info.backlinks", "items.avg_backlinks_info.dofollow", "items.avg_backlinks_info.last_updated_time", "items.avg_backlinks_info.main_domain_rank", "items.avg_backlinks_info.rank", "items.avg_backlinks_info.referring_domains", "items.avg_backlinks_info.referring_main_domains", "items.avg_backlinks_info.referring_pages", "items.avg_backlinks_info.se_type", "items.keyword", "items.keyword_info.categories", "items.keyword_info.competition", "items.keyword_info.competition_level", "items.keyword_info.cpc", "items.keyword_info.high_top_of_page_bid", "items.keyword_info.last_updated_time", "items.keyword_info.low_top_of_page_bid", "items.keyword_info.monthly_searches", "items.keyword_info.se_type", "items.keyword_info.search_volume", "items.keyword_info.search_volume_trend.monthly", "items.keyword_info.search_volume_trend.yearly", "items.keyword_properties.core_keyword", "items.keyword_properties.detected_language", "items.keyword_properties.keyword_difficulty", "items.keyword_properties.se_type", "items.keyword_properties.synonym_clustering_algorithm", "items.language_code", "items.location_code", "items.se_type", "items.search_intent_info.foreign_intent", "items.search_intent_info.last_updated_time", "items.search_intent_info.main_intent", "items.search_intent_info.se_type", "status_code", "status_message" ], "dataforseo_labs_google_historical_serp": [ "datetime", "items.domain", "items.rank_absolute", "items.title", "items.type" ], "dataforseo_labs_google_serp_competitors": [ "id", "items.avg_position", "items.domain", "items.etv", "items.keywords_count", "items.keywords_positions.python programming", "items.median_position", "items.rating", "items.relevant_serp_items", "items.se_type", "items.visibility", "status_code", "status_message" ], "dataforseo_labs_bulk_keyword_difficulty": [ "id", "items.keyword", "items.keyword_difficulty", "items.se_type", "status_code", "status_message" ], "dataforseo_labs_google_subdomains": [ "id", "items.metrics.*.count", "items.metrics.*.estimated_paid_traffic_cost", "items.metrics.*.etv", "items.metrics.*.is_down", "items.metrics.*.is_new", "items.metrics.*.is_up", "items.metrics.*.pos_1", "items.metrics.*.pos_11_20", "items.metrics.*.pos_21_30", "items.metrics.*.pos_2_3", "items.metrics.*.pos_31_40", "items.metrics.*.pos_41_50", "items.metrics.*.pos_4_10", "items.metrics.*.pos_51_60", "items.metrics.*.pos_61_70", "items.metrics.*.pos_71_80", "items.metrics.*.pos_81_90", "items.metrics.*.pos_91_100", "items.se_type", "items.subdomain", "status_code", "status_message" ], "dataforseo_labs_google_keyword_overview": [ "id", "items.avg_backlinks_info.backlinks", "items.avg_backlinks_info.dofollow", "items.avg_backlinks_info.last_updated_time", "items.avg_backlinks_info.main_domain_rank", "items.avg_backlinks_info.rank", "items.avg_backlinks_info.referring_domains", "items.avg_backlinks_info.referring_main_domains", "items.avg_backlinks_info.referring_pages", "items.avg_backlinks_info.se_type", "items.keyword", "items.keyword_info.categories", "items.keyword_info.competition", "items.keyword_info.competition_level", "items.keyword_info.cpc", "items.keyword_info.high_top_of_page_bid", "items.keyword_info.last_updated_time", "items.keyword_info.low_top_of_page_bid", "items.keyword_info.monthly_searches", "items.keyword_info.se_type", "items.keyword_info.search_volume", "items.keyword_properties.detected_language", "items.keyword_properties.keyword_difficulty", "items.keyword_properties.se_type", "items.keyword_properties.synonym_clustering_algorithm", "items.language_code", "items.location_code", "items.se_type", "items.search_intent_info.foreign_intent", "items.search_intent_info.last_updated_time", "items.search_intent_info.main_intent", "items.search_intent_info.se_type", "status_code", "status_message" ], "dataforseo_labs_google_top_searches": [ "id", "items.avg_backlinks_info.backlinks", "items.avg_backlinks_info.dofollow", "items.avg_backlinks_info.last_updated_time", "items.avg_backlinks_info.main_domain_rank", "items.avg_backlinks_info.rank", "items.avg_backlinks_info.referring_domains", "items.avg_backlinks_info.referring_main_domains", "items.avg_backlinks_info.referring_pages", "items.avg_backlinks_info.se_type", "items.keyword", "items.keyword_info.categories", "items.keyword_info.competition", "items.keyword_info.competition_level", "items.keyword_info.cpc", "items.keyword_info.high_top_of_page_bid", "items.keyword_info.last_updated_time", "items.keyword_info.low_top_of_page_bid", "items.keyword_info.monthly_searches", "items.keyword_info.se_type", "items.keyword_info.search_volume", "items.keyword_info.search_volume_trend.quarterly", "items.keyword_info.search_volume_trend.yearly", "items.keyword_properties.core_keyword", "items.keyword_properties.detected_language", "items.keyword_properties.is_another_language", "items.keyword_properties.keyword_difficulty", "items.keyword_properties.se_type", "items.keyword_properties.synonym_clustering_algorithm", "items.language_code", "items.location_code", "items.se_type", "items.search_intent_info.foreign_intent", "items.search_intent_info.last_updated_time", "items.search_intent_info.main_intent", "items.search_intent_info.se_type", "status_code", "status_message" ], "dataforseo_labs_search_intent": [ "id", "items.keyword", "items.keyword_intent.label", "items.keyword_intent.probability", "items.secondary_keyword_intents.label", "items.secondary_keyword_intents.probability", "status_code", "status_message" ], "dataforseo_labs_google_keywords_for_site": [ "id", "items.avg_backlinks_info.backlinks", "items.avg_backlinks_info.dofollow", "items.avg_backlinks_info.last_updated_time", "items.avg_backlinks_info.main_domain_rank", "items.avg_backlinks_info.rank", "items.avg_backlinks_info.referring_domains", "items.avg_backlinks_info.referring_main_domains", "items.avg_backlinks_info.referring_pages", "items.avg_backlinks_info.se_type", "items.keyword", "items.keyword_info.categories", "items.keyword_info.competition", "items.keyword_info.competition_level", "items.keyword_info.cpc", "items.keyword_info.high_top_of_page_bid", "items.keyword_info.last_updated_time", "items.keyword_info.low_top_of_page_bid", "items.keyword_info.monthly_searches", "items.keyword_info.se_type", "items.keyword_info.search_volume", "items.keyword_info.search_volume_trend.monthly", "items.keyword_info.search_volume_trend.quarterly", "items.keyword_info.search_volume_trend.yearly", "items.keyword_properties.detected_language", "items.keyword_properties.keyword_difficulty", "items.keyword_properties.se_type", "items.keyword_properties.synonym_clustering_algorithm", "items.language_code", "items.location_code", "items.se_type", "items.search_intent_info.foreign_intent", "items.search_intent_info.last_updated_time", "items.search_intent_info.main_intent", "items.search_intent_info.se_type", "status_code", "status_message" ], "dataforseo_labs_google_domain_intersection": [ "id", "items", "status_code", "status_message" ], "dataforseo_labs_google_historical_rank_overview": [ "id", "items.metrics.*.count", "items.metrics.*.estimated_paid_traffic_cost", "items.metrics.*.etv", "items.metrics.*.is_down", "items.metrics.*.is_lost", "items.metrics.*.is_new", "items.metrics.*.is_up", "items.metrics.*.pos_1", "items.metrics.*.pos_11_20", "items.metrics.*.pos_21_30", "items.metrics.*.pos_2_3", "items.metrics.*.pos_31_40", "items.metrics.*.pos_41_50", "items.metrics.*.pos_4_10", "items.metrics.*.pos_51_60", "items.metrics.*.pos_61_70", "items.metrics.*.pos_71_80", "items.metrics.*.pos_81_90", "items.metrics.*.pos_91_100", "items.month", "items.se_type", "items.year", "status_code", "status_message" ], "dataforseo_labs_bulk_traffic_estimation": [ "id", "items.se_type", "items.target", "status_code", "status_message" ], "dataforseo_labs_available_filters": [], "dataforseo_labs_google_historical_keyword_data": [ "id", "items.history.keyword_info.categories", "items.history.keyword_info.competition", "items.history.keyword_info.competition_level", "items.history.keyword_info.cpc", "items.history.keyword_info.high_top_of_page_bid", "items.history.keyword_info.last_updated_time", "items.history.keyword_info.low_top_of_page_bid", "items.history.keyword_info.monthly_searches", "items.history.keyword_info.se_type", "items.history.keyword_info.search_volume", "items.history.keyword_info.search_volume_trend.monthly", "items.history.keyword_info.search_volume_trend.quarterly", "items.history.keyword_info.search_volume_trend.yearly", "items.history.month", "items.history.year", "items.keyword", "items.language_code", "items.location_code", "items.se_type", "status_code", "status_message" ], "backlinks_backlinks": [ "id", "items.anchor", "items.backlink_spam_score", "items.dofollow", "items.domain_from", "items.domain_from_country", "items.domain_from_ip", "items.domain_from_platform_type", "items.domain_from_rank", "items.domain_to", "items.first_seen", "items.is_broken", "items.is_new", "items.item_type", "items.last_seen", "items.links_count", "items.original", "items.page_from_encoding", "items.page_from_external_links", "items.page_from_internal_links", "items.page_from_language", "items.page_from_rank", "items.page_from_size", "items.page_from_status_code", "items.page_from_title", "items.prev_seen", "items.rank", "items.ranked_keywords_info.page_from_keywords_count_top_10", "items.ranked_keywords_info.page_from_keywords_count_top_100", "items.ranked_keywords_info.page_from_keywords_count_top_3", "items.semantic_location", "items.text_post", "items.text_pre", "items.tld_from", "items.type", "items.url_from", "items.url_from_https", "items.url_to", "items.url_to_https", "items.url_to_spam_score", "items.url_to_status_code", "status_code", "status_message" ], "backlinks_anchors": [ "id", "items.anchor", "items.backlinks", "items.backlinks_spam_score", "items.broken_backlinks", "items.broken_pages", "items.first_seen", "items.rank", "items.referring_domains", "items.referring_domains_nofollow", "items.referring_ips", "items.referring_links_attributes", "items.referring_links_countries", "items.referring_links_platform_types", "items.referring_links_semantic_locations", "items.referring_links_tld", "items.referring_links_types", "items.referring_main_domains", "items.referring_main_domains_nofollow", "items.referring_pages", "items.referring_pages_nofollow", "items.referring_subnets", "items.type", "status_code", "status_message" ], "backlinks_competitors": [ "id", "items.intersections", "items.rank", "items.target", "items.type", "status_code", "status_message" ], "backlinks_domain_intersection": [ "id", "items.domain_intersection.*.backlinks", "items.domain_intersection.*.broken_backlinks", "items.domain_intersection.*.broken_pages", "items.domain_intersection.*.first_seen", "items.domain_intersection.*.rank", "items.domain_intersection.*.referring_domains", "items.domain_intersection.*.referring_ips", "items.domain_intersection.*.referring_links_platform_types", "items.domain_intersection.*.referring_links_semantic_locations", "items.domain_intersection.*.referring_links_tld", "items.domain_intersection.*.referring_links_types.anchor", "items.domain_intersection.*.referring_links_types.image", "items.domain_intersection.*.referring_main_domains", "items.domain_intersection.*.referring_pages", "items.domain_intersection.*.referring_subnets", "items.domain_intersection.*.target", "items.domain_intersection.*.type", "items.summary.intersections_count", "status_code", "status_message" ], "backlinks_domain_pages_summary": [ "id", "items.backlinks", "items.backlinks_spam_score", "items.broken_backlinks", "items.broken_pages", "items.first_seen", "items.rank", "items.referring_domains", "items.referring_domains_nofollow", "items.referring_ips", "items.referring_links_attributes", "items.referring_links_countries", "items.referring_links_platform_types", "items.referring_links_semantic_locations", "items.referring_links_tld", "items.referring_links_types.alternate", "items.referring_links_types.anchor", "items.referring_links_types.canonical", "items.referring_links_types.image", "items.referring_links_types.redirect", "items.referring_main_domains", "items.referring_main_domains_nofollow", "items.referring_pages", "items.referring_pages_nofollow", "items.referring_subnets", "items.type", "items.url", "status_code", "status_message" ], "backlinks_domain_pages": [ "id", "items.content_encoding", "items.domain", "items.encoded_size", "items.fetch_time", "items.first_visited", "items.ip", "items.main_domain", "items.media_type", "items.meta.charset", "items.meta.external_links_count", "items.meta.h1", "items.meta.page_spam_score", "items.meta.platform_type", "items.meta.title", "items.meta.words_count", "items.page", "items.page_summary.backlinks", "items.page_summary.backlinks_spam_score", "items.page_summary.broken_backlinks", "items.page_summary.broken_pages", "items.page_summary.first_seen", "items.page_summary.rank", "items.page_summary.referring_domains", "items.page_summary.referring_domains_nofollow", "items.page_summary.referring_ips", "items.page_summary.referring_links_attributes", "items.page_summary.referring_links_countries", "items.page_summary.referring_links_platform_types", "items.page_summary.referring_links_semantic_locations", "items.page_summary.referring_links_tld", "items.page_summary.referring_links_types.alternate", "items.page_summary.referring_links_types.anchor", "items.page_summary.referring_links_types.canonical", "items.page_summary.referring_links_types.image", "items.page_summary.referring_links_types.redirect", "items.page_summary.referring_main_domains", "items.page_summary.referring_main_domains_nofollow", "items.page_summary.referring_pages", "items.page_summary.referring_pages_nofollow", "items.page_summary.referring_subnets", "items.prev_visited", "items.server", "items.size", "items.status_code", "items.tld", "items.type", "status_code", "status_message" ], "backlinks_page_intersection": [ "id", "items.page_intersection.*.alt", "items.page_intersection.*.anchor", "items.page_intersection.*.attributes", "items.page_intersection.*.backlink_spam_score", "items.page_intersection.*.dofollow", "items.page_intersection.*.domain_from", "items.page_intersection.*.domain_from_country", "items.page_intersection.*.domain_from_ip", "items.page_intersection.*.domain_from_platform_type", "items.page_intersection.*.domain_from_rank", "items.page_intersection.*.domain_to", "items.page_intersection.*.first_seen", "items.page_intersection.*.is_broken", "items.page_intersection.*.is_new", "items.page_intersection.*.item_type", "items.page_intersection.*.last_seen", "items.page_intersection.*.links_count", "items.page_intersection.*.original", "items.page_intersection.*.page_from_encoding", "items.page_intersection.*.page_from_external_links", "items.page_intersection.*.page_from_internal_links", "items.page_intersection.*.page_from_language", "items.page_intersection.*.page_from_rank", "items.page_intersection.*.page_from_size", "items.page_intersection.*.page_from_status_code", "items.page_intersection.*.page_from_title", "items.page_intersection.*.prev_seen", "items.page_intersection.*.rank", "items.page_intersection.*.semantic_location", "items.page_intersection.*.text_post", "items.page_intersection.*.text_pre", "items.page_intersection.*.tld_from", "items.page_intersection.*.type", "items.page_intersection.*.url_from", "items.page_intersection.*.url_from_https", "items.page_intersection.*.url_to", "items.page_intersection.*.url_to_https", "items.page_intersection.*.url_to_spam_score", "items.page_intersection.*.url_to_status_code", "items.summary.intersections_count", "status_code", "status_message" ], "backlinks_referring_domains": [ "id", "items.backlinks", "items.backlinks_spam_score", "items.broken_backlinks", "items.broken_pages", "items.domain", "items.first_seen", "items.rank", "items.referring_domains", "items.referring_ips", "items.referring_links_attributes", "items.referring_links_countries", "items.referring_links_platform_types", "items.referring_links_semantic_locations", "items.referring_links_tld", "items.referring_links_types.alternate", "items.referring_links_types.anchor", "items.referring_links_types.image", "items.referring_main_domains", "items.referring_pages", "items.referring_pages_nofollow", "items.referring_subnets", "items.type", "status_code", "status_message" ], "backlinks_referring_networks": [ "id", "items.backlinks", "items.broken_backlinks", "items.broken_pages", "items.first_seen", "items.network_address", "items.rank", "items.referring_domains", "items.referring_ips", "items.referring_links_attributes", "items.referring_links_countries", "items.referring_links_platform_types", "items.referring_links_semantic_locations", "items.referring_links_tld", "items.referring_links_types.alternate", "items.referring_links_types.anchor", "items.referring_links_types.image", "items.referring_main_domains", "items.referring_pages", "items.referring_pages_nofollow", "items.referring_subnets", "items.type", "status_code", "status_message" ], "backlinks_summary": [ "id", "items.backlinks", "items.backlinks_spam_score", "items.broken_backlinks", "items.broken_pages", "items.crawled_pages", "items.external_links_count", "items.first_seen", "items.info.country", "items.info.ip_address", "items.info.platform_type", "items.info.server", "items.info.target_spam_score", "items.rank", "items.referring_domains", "items.referring_domains_nofollow", "items.referring_ips", "items.referring_links_attributes", "items.referring_links_countries", "items.referring_links_platform_types", "items.referring_links_semantic_locations", "items.referring_links_tld", "items.referring_links_types.alternate", "items.referring_links_types.anchor", "items.referring_links_types.canonical", "items.referring_links_types.image", "items.referring_links_types.redirect", "items.referring_main_domains", "items.referring_main_domains_nofollow", "items.referring_pages", "items.referring_pages_nofollow", "items.referring_subnets", "items.target", "status_code", "status_message" ], "backlinks_timeseries_new_lost_summary": [ "id", "items.date", "items.lost_backlinks", "items.lost_referring_domains", "items.lost_referring_main_domains", "items.new_backlinks", "items.new_referring_domains", "items.new_referring_main_domains", "items.type", "status_code", "status_message" ], "backlinks_timeseries_summary": [ "id", "items.backlinks", "items.backlinks_nofollow", "items.date", "items.rank", "items.referring_domains", "items.referring_domains_nofollow", "items.referring_ips", "items.referring_main_domains", "items.referring_main_domains_nofollow", "items.referring_pages", "items.referring_pages_nofollow", "items.referring_subnets", "items.type", "status_code", "status_message" ], "business_data_business_listings_search": [ "id", "items.additional_categories", "items.address", "items.address_info.address", "items.address_info.borough", "items.address_info.city", "items.address_info.country_code", "items.address_info.region", "items.address_info.zip", "items.attributes.available_attributes", "items.category", "items.category_ids", "items.check_url", "items.cid", "items.contact_info.source", "items.contact_info.type", "items.contact_info.value", "items.description", "items.domain", "items.feature_id", "items.first_seen", "items.is_claimed", "items.last_updated_time", "items.latitude", "items.logo", "items.longitude", "items.main_image", "items.original_title", "items.people_also_search.cid", "items.people_also_search.feature_id", "items.people_also_search.rating.rating_type", "items.people_also_search.rating.value", "items.people_also_search.rating.votes_count", "items.people_also_search.title", "items.phone", "items.place_id", "items.place_topics", "items.popular_times.popular_times_by_days", "items.rating.rating_type", "items.rating.value", "items.rating.votes_count", "items.rating_distribution", "items.snippet", "items.title", "items.total_photos", "items.type", "items.url", "items.work_time.work_hours", "status_code", "status_message" ], "domain_analytics_whois_overview": [ "id", "items.backlinks_info.backlinks", "items.backlinks_info.dofollow", "items.backlinks_info.referring_domains", "items.backlinks_info.referring_main_domains", "items.backlinks_info.referring_pages", "items.backlinks_info.time_update", "items.changed_datetime", "items.created_datetime", "items.domain", "items.epp_status_codes", "items.expiration_datetime", "items.first_seen", "items.metrics.*.count", "items.metrics.*.estimated_paid_traffic_cost", "items.metrics.*.etv", "items.metrics.*.pos_1", "items.metrics.*.pos_11_20", "items.metrics.*.pos_21_30", "items.metrics.*.pos_2_3", "items.metrics.*.pos_31_40", "items.metrics.*.pos_41_50", "items.metrics.*.pos_4_10", "items.metrics.*.pos_51_60", "items.metrics.*.pos_61_70", "items.metrics.*.pos_71_80", "items.metrics.*.pos_81_90", "items.metrics.*.pos_91_100", "items.registered", "items.registrar", "items.tld", "items.updated_datetime", "status_code", "status_message" ], "domain_analytics_whois_available_filters": [], "domain_analytics_technologies_domain_technologies": [ "id", "items.content_language_code", "items.country_iso_code", "items.domain", "items.domain_rank", "items.last_visited", "items.technologies", "items.title", "items.type", "status_code", "status_message" ], "domain_analytics_technologies_available_filters": [], "content_analysis_search": [ "id", "items.content_info.author", "items.content_info.connotation_types", "items.content_info.content_quality_score", "items.content_info.content_type", "items.content_info.date_published", "items.content_info.group_date", "items.content_info.language", "items.content_info.level", "items.content_info.main_title", "items.content_info.previous_title", "items.content_info.semantic_location", "items.content_info.sentiment_connotations", "items.content_info.snippet", "items.content_info.snippet_length", "items.content_info.text_category", "items.content_info.title", "items.country", "items.domain", "items.domain_rank", "items.fetch_time", "items.language", "items.main_domain", "items.page_category", "items.page_types", "items.score", "items.type", "items.url", "items.url_rank", "status_code", "status_message" ], "content_analysis_summary": [ "id", "items.connotation_types", "items.countries", "items.languages", "items.page_categories.category", "items.page_categories.count", "items.page_types", "items.rank", "items.sentiment_connotations", "items.text_categories.category", "items.text_categories.count", "items.top_domains.count", "items.top_domains.domain", "items.total_count", "items.type", "status_code", "status_message" ], "content_analysis_phrase_trends": [ "id", "items.connotation_types", "items.countries", "items.date", "items.languages", "items.page_categories.category", "items.page_categories.count", "items.page_types", "items.rank", "items.sentiment_connotations", "items.text_categories.category", "items.text_categories.count", "items.top_domains.count", "items.top_domains.domain", "items.total_count", "items.type", "status_code", "status_message" ] } } ```