Skip to main content

📍 Semrush - Position Tracking

Overview

Monitor keyword rankings over time with historical position data, ranking distribution summaries, and URL-level tracking. Includes subscription-based limits and intelligent sorting.

Position Tracking

Get Keyword Positions

GET /keyword/tracking/:domain

Purpose: Track keyword rankings with historical data and ranking distribution

Request:

GET /v1/integrations/semrush/keyword/tracking/example.com?config_id=507f1f77bcf86cd799439011&limit=25&page=1&sortBy=current_rank&sortOrder=asc&new=false
Authorization: Bearer {jwt_token}

Query Parameters:

ParameterTypeDefaultDescription
config_idStringRequiredProject configuration ID
limitInteger25Keywords per page (1-100)
pageInteger1Page number
sortByStringcurrent_rankSort field (see below)
sortOrderStringascasc or desc
newBooleanfalseForce refresh from API

Sort Fields:

  • current_rank - Current SERP position
  • start_rank - Initial position when tracking started
  • difference - Position change (start vs current)
  • keyword - Alphabetical

Subscription Required: Active SEO subscription (Pro/Plus/Platinum)

Semrush API:

GET https://api.semrush.com/reports/v1/projects/{CAMPAIGN_ID}/tracking?key={API_KEY}&action=report&type=tracking_position_organic&display_sort=0_pos_asc&url={FORMATTED_URL}&display_limit=200

URL Formatting:

  • Main domain: *.example.com/*
  • Subdomain: sub.example.com/*

JSON Response:

{
"success": true,
"message": "SUCCESS",
"data": {
"data": {
"project_name": "Client Website SEO",
"project_domain": "example.com",
"keywords": [
{
"keyword": "quality products",
"start_date": "2025-01-15",
"start_rank": 15,
"current_rank": 8,
"difference": -7,
"url": "https://example.com/products"
},
{
"keyword": "best deals online",
"start_date": "2025-01-15",
"start_rank": 25,
"current_rank": 3,
"difference": -22,
"url": "https://example.com/deals"
},
{
"keyword": "free shipping",
"start_date": "2025-01-20",
"start_rank": 12,
"current_rank": 1,
"difference": -11,
"url": "https://example.com/shipping"
}
],
"keyword_summary": {
"rank_1": 1,
"rank_2_5": 2,
"rank_6_10": 5,
"rank_10+": 12
}
},
"pagination": {
"currentPage": 1,
"totalPages": 2,
"totalItems": 20,
"itemsPerPage": 25,
"hasNextPage": false,
"hasPreviousPage": false
},
"totalCount": 20,
"createdAt": "2025-10-10T08:00:00Z",
"lastUpdate": "2025-10-10T08:00:00Z",
"nextUpdate": "2025-10-17T08:00:00Z",
"daysSinceUpdate": 0,
"daysUntilUpdate": 7,
"isStale": false
}
}

Keyword Fields

FieldTypeDescription
keywordStringSearch query being tracked
start_dateDateWhen tracking began (YYYY-MM-DD)
start_rankIntegerInitial position
current_rankIntegerCurrent position
differenceIntegerPosition change (negative = improvement)
urlStringPage ranking for keyword

Keyword Summary

Ranking Distribution

Automatically calculated based on current positions:

CategoryDescriptionCount
rank_1Position 1 (top spot)Integer
rank_2_5Positions 2-5 (page 1 top)Integer
rank_6_10Positions 6-10 (page 1 bottom)Integer
rank_10+Beyond position 10 (page 2+)Integer

Use Case: Quickly assess ranking performance

Subscription Plans

Keyword Limits

PlanKeyword LimitAccess Level
Pro10 keywordsEntry-level tracking
Plus20 keywordsStandard tracking
Platinum40 keywordsAdvanced tracking
InternalUnlimitedDashClicks staff only

Limit Enforcement

  • Pagination respects plan limits
  • totalCount capped at plan limit for external users
  • Keyword summary calculated on limited dataset
  • Sorting applied before limiting

Example (Pro plan, 10 keyword limit):

// Request with limit=25, but only 10 returned
{
"pagination": {
"totalItems": 10, // Capped at plan limit
"itemsPerPage": 25
},
"totalCount": 10
}

Caching Strategy

Cache Configuration:

  • Cache Type: keyword_position_tracking_organic
  • TTL: 7 days (weekly data)
  • Force Refresh: ?new=true

MongoDB Aggregation:

// Efficient sorting and pagination
getSortedKeywordData({
accountID,
domain,
sortBy: 'current_rank',
sortOrder: 'asc',
limit: 25,
skip: 0,
maxKeywords: 20, // Plan limit
isPlanLimitedUser: true,
});

Use Cases

1. Weekly Ranking Report

Track progress over time:

// Force weekly refresh
GET /keyword/tracking/client.com?config_id={id}&new=true

// Calculate improvements
keywords.filter(k => k.difference < 0).length; // Improved keywords
keywords.filter(k => k.difference > 0).length; // Declined keywords

2. Top Rankings Dashboard

Show best-performing keywords:

GET /keyword/tracking/client.com?config_id={id}&sortBy=current_rank&sortOrder=asc&limit=10

// Display top 10 ranking keywords

3. Progress Monitoring

Track position changes:

keywords.forEach(k => {
const change = Math.abs(k.difference);
const direction = k.difference < 0 ? 'improved' : 'declined';
console.log(`${k.keyword}: ${direction} by ${change} positions`);
});

4. Ranking Distribution Analysis

Visualize performance:

const summary = response.data.data.keyword_summary;
const total = summary.rank_1 + summary.rank_2_5 + summary.rank_6_10 + summary['rank_10+'];

console.log(`Top 1: ${((summary.rank_1 / total) * 100).toFixed(1)}%`);
console.log(`Top 5: ${(((summary.rank_1 + summary.rank_2_5) / total) * 100).toFixed(1)}%`);
console.log(
`Page 1: ${(((summary.rank_1 + summary.rank_2_5 + summary.rank_6_10) / total) * 100).toFixed(
1,
)}%`,
);

5. URL Performance

Track which pages rank:

const urlPerformance = {};
keywords.forEach(k => {
if (!urlPerformance[k.url]) {
urlPerformance[k.url] = { keywords: 0, avgRank: 0 };
}
urlPerformance[k.url].keywords++;
urlPerformance[k.url].avgRank += k.current_rank;
});

// Calculate averages
Object.keys(urlPerformance).forEach(url => {
urlPerformance[url].avgRank /= urlPerformance[url].keywords;
});

Error Handling

Missing Configuration

{
"success": false,
"message": "Invalid project configuration provided",
"statusCode": 400
}

Solution: Create project configuration first

No Subscription

{
"success": false,
"error_code": "SUBSCRIPTION_REQUIRED",
"message": "SEO subscription required to access this feature",
"statusCode": 403
}

Solution: Activate SEO subscription (Pro/Plus/Platinum)

Invalid Campaign

{
"success": false,
"message": "ERROR 40 :: Campaign not found",
"statusCode": 500
}

Solution: Verify campaign_id in project configuration

Best Practices

Refresh Frequency

  • Weekly: Standard refresh cycle (matches Semrush update)
  • 🔄 Force Refresh: Use sparingly to avoid rate limits
  • 📊 Cached Data: Acceptable for daily reports

Sorting Strategy

  • Dashboard: Sort by current_rank ASC (show best first)
  • Progress Report: Sort by difference ASC (biggest improvements)
  • Alphabetical: Sort by keyword ASC (organized lists)

Plan Management

  • 🎯 Pro (10 keywords): Focus on highest-value keywords
  • 📈 Plus (20 keywords): Mix of primary and secondary keywords
  • 💎 Platinum (40 keywords): Comprehensive tracking
💬

Documentation Assistant

Ask me anything about the docs

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

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

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