domain_organic_search
Analyze organic search keywords driving traffic to any domain to identify ranking opportunities and optimize SEO strategies.
Instructions
Get organic search keywords for a domain
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| domain | Yes | ||
| database | No | ||
| limit | No | ||
| offset | No |
Implementation Reference
- src/index.ts:268-277 (handler)The handler function for the 'domain_organic_search' tool. It validates input using the schema, then calls the Semrush API with the 'domain_organic' report type and specified parameters to retrieve organic search keywords for the given domain.case 'domain_organic_search': { const { domain, database, limit, offset } = DomainOrganicSearchSchema.parse(args); data = await callSemrushAPI('domain_organic', { domain, database, display_limit: limit, display_offset: offset, export_columns: 'Ph,Po,Nq,Cp,Ur,Tr,Tc,Co,Nr,Td', }); break;
- src/index.ts:72-77 (schema)Zod schema defining the input parameters for the domain_organic_search tool, including domain, database, limit, and offset.const DomainOrganicSearchSchema = z.object({ domain: z.string().describe('Domain to analyze'), database: z.string().default('us').describe('Database code'), limit: z.coerce.number().default(10).describe('Number of results'), offset: z.coerce.number().default(0).describe('Offset for pagination'), });
- src/index.ts:193-200 (registration)Registration of the 'domain_organic_search' tool in the ListTools response, including name, description, and input schema reference.name: 'domain_organic_search', description: 'Get organic search keywords for a domain', inputSchema: { type: 'object', properties: DomainOrganicSearchSchema.shape, required: ['domain'], }, },
- src/index.ts:103-155 (helper)Shared helper function that performs API calls to Semrush, handles both standard and analytics/v1 endpoints, parses CSV responses using parseCSV, and manages errors.async function callSemrushAPI(reportType: string, params: Record<string, any>, isAnalyticsV1: boolean = false) { try { let requestUrl: string; let queryParams: Record<string, any>; if (isAnalyticsV1) { requestUrl = `${SEMRUSH_API_BASE}/analytics/v1/`; // Note the trailing slash for consistency queryParams = { key: API_KEY, type: reportType, // e.g., 'backlinks_overview' ...params, }; } else { requestUrl = SEMRUSH_API_BASE + '/'; // Main API endpoint: https://api.semrush.com/ queryParams = { key: API_KEY, type: reportType, // e.g., 'domain_ranks' ...params, }; } console.error(`Calling Semrush API. URL: ${requestUrl}, Params: ${JSON.stringify(queryParams)}`); const response = await axios.get(requestUrl, { params: queryParams, }); if (typeof response.data === 'string') { const parsed = parseCSV(response.data); // If CSV parsing itself returns an error object, or if it's an array but empty (parsed from only headers or error string) if (parsed && typeof parsed === 'object' && ('error' in parsed || (Array.isArray(parsed) && 'headers' in parsed && parsed.length === 0))) { return parsed; // Return the error object or {error: ..., headers:..., data: []} } return parsed; // Otherwise, return the array of parsed objects or results from parseCSV } return response.data; // If not a string, return as is (might be JSON error from API) } catch (error: any) { console.error(`Semrush API call failed for type/path ${reportType}. Error: ${error.message}`); if (error.response) { console.error('Semrush API Error Response Status:', error.response.status); console.error('Semrush API Error Response Data:', error.response.data); let errorMessage = error.response.data; if (typeof errorMessage === 'string') { const semrushErrorMatch = errorMessage.match(/ERROR :: (.+)/); if (semrushErrorMatch && semrushErrorMatch[1]) { errorMessage = semrushErrorMatch[1]; } } throw new Error(`Semrush API error (${error.response.status}): ${errorMessage || error.response.statusText}`); } throw new Error(`Semrush API request failed: ${error.message}`); } }
- src/index.ts:25-59 (helper)Helper function to parse semicolon-delimited CSV data returned by Semrush API into an array of objects or error object.function parseCSV(csvData: string): Record<string, any>[] | { error: string } { if (!csvData || typeof csvData !== 'string') { return { error: 'Invalid or empty CSV data' }; } try { const lines = csvData.trim().split('\n').filter(line => line.trim().length > 0); if (lines.length === 0) { return { error: 'CSV data is empty after trimming' }; } if (lines.length === 1 && lines[0].startsWith('ERROR ::')) { return { error: lines[0] }; } if (lines.length <= 1 && !lines[0].includes(';')) { // Likely not a valid CSV if only one line and no delimiter return { error: 'No data rows found or invalid CSV header' }; } const headers = lines[0].split(';'); const dataRows = lines.slice(1); if (dataRows.length === 0 && lines.length === 1) { // Only header row means no data // Return headers so client knows what fields were expected, but indicate no data return { error: 'No data results, only headers returned' }; } const results = dataRows.map(line => { const values = line.split(';'); const row: Record<string, string> = {}; headers.forEach((header, index) => { row[header.trim()] = values[index]?.trim() || ''; }); return row; }); return results; } catch (error: any) { console.error('Error parsing CSV:', error.message); return { error: 'Failed to parse CSV data' }; } }